/
CachingDynamicContentImpl.java
220 lines (192 loc) · 8.11 KB
/
CachingDynamicContentImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.appclient.server.core.jws.servedcontent;
import java.util.LinkedList;
import java.util.Properties;
import org.glassfish.appclient.server.core.jws.Util;
/**
*
* @author tjquinn
*/
/**
*Represents dynamic content in template form.
*<p>
*This class also keeps track of the most recent times the object's template
*was used to generate content that was different from the result of the
*previous generation using this template. This
*information is used in responding to HTTP HEAD requests. Java Web Start uses
*HEAD requests to find out if a document on the server is more recent than
*the locally cached copy on the client. If so, then Java Web Start will
*request the updated version with a routine GET request.
*<p>
*To avoid incorrectly reporting obsolete cached documents as current, this
*class keeps track of when the content generated by the template is different
*from the previous generation.
*<p>
*The generated content can depend on request-time information
*(such as command line arguments passed in the query string of the HTTP request).
*We save and track only a few individual response instances, because the assumption
*is that requests that carry query strings (which are converted into command line
*arguments passed to ACC and on through to the app client) are likely to change
*frequently and not necessarily be reused often. Keeping a few allows caching
*the different content resulting from a small number of different argument
*value settings, but avoids the problems of caching every single response
*which could become a large memory drain if each request specified a
*different set of arguments (for instance, one of the arguments could be a
*timestamp that would change every time).
*/
public class CachingDynamicContentImpl extends Content.Adapter implements DynamicContent {
/** maximum number of instances of content to keep for each template */
private static final int DEFAULT_MAX_INSTANCES = 4;
/**
*the template which will be used at runtime to create the actual response
*to the HTTP request
*/
private final String template;
/** the MIME type of the data represented by this CachingDynamicContentImpl instance */
protected final String mimeType;
/** content instances resulting from previous HTTP GET requests */
private final LinkedList<Instance> instances = new LinkedList<Instance>();
/** max. number of instances to cache */
private final int maxInstances;
/**
* Returns a new instance of CachingDynamicContentImpl.
* @param origin the ContentOrigin for the new content instance
* @param contentKey the content key used to store and retrieve the content
* @param path the path relative to the subcategory in which this document is addressable
* @param mimeType the MIME type of data represented by the content generated by this
* object.
* @return new CachingDynamicContentImpl object
*/
public CachingDynamicContentImpl(final String template, final String mimeType) {
this(template, mimeType, DEFAULT_MAX_INSTANCES);
}
public CachingDynamicContentImpl(final String template, final String mimeType,
final int maxInstances) {
this.template = template;
this.mimeType = mimeType;
this.maxInstances = maxInstances;
}
/**
* Returns the CachingDynamicContentImpl.InstanceImpl for this template corresponding to
* the specified substitution token values.
* @param tokenValues the name/value pairs to be substituted in the template
* @param createIfAbsent selects whether a new CachingDynamicContentImpl.InstanceImpl should
* be created for the resulting text if the content text resulting from the
* substitution is not already cached by this CachingDynamicContentImpl.
* @return the instance corresponding to the content generated by the tokenValues;
* null if no such instance already exists for this CachingDynamicContentImpl and createIfAbsent
* was false.
*/
public Instance getExistingInstance(final Properties tokenValues) {
return getOrCreateInstance(tokenValues, false);
}
public Instance getOrCreateInstance(final Properties tokenValues) {
return getOrCreateInstance(tokenValues, true);
}
@Override
public boolean isMain() {
return false;
}
private Instance getOrCreateInstance(final Properties tokenValues, final boolean createIfAbsent) {
/*
* I generally avoid passing in flags to control the inner flow of a
* method, but in this case we want the search and the optional addition
* to take place atomically inside the synchronized block. This way
* the synchronized clause is in one place.
*/
final String textWithPlaceholdersReplaced =
Util.replaceTokens(template, tokenValues);
/*
* Look for an instance with its text matching the just-computed
* replacement text.
*/
Instance result = null;
synchronized (instances) {
for (Instance i : instances) {
if (i.getText().equals(textWithPlaceholdersReplaced)) {
return i;
}
}
if (createIfAbsent) {
result = new InstanceAdapter(textWithPlaceholdersReplaced);
addInstance(result);
}
}
return result;
}
/**
* Adds a new content instance to this dynamic content. If adding the instance
* makes the cache too long, discards the oldest instance.
* @param newInstance the new instance to be added to the cache
*/
private void addInstance(final Instance newInstance) {
synchronized (instances) {
instances.addFirst(newInstance);
if (instances.size() > maxInstances) {
instances.removeLast();
}
}
}
/**
* Returns the MIME type associated with this content.
* @return the MIME type for this content
*/
public String getMimeType() {
return mimeType;
}
/**
* Clears the cached instances.
*/
protected void clearInstances() {
instances.clear();
}
/**
* Returns a string representation of the CachingDynamicContentImpl.
*/
@Override
public String toString() {
return super.toString() + ", template=" + template + ", MIME type=" + mimeType;// + ", most recent change in generated content=" + timestamp;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CachingDynamicContentImpl other = (CachingDynamicContentImpl) obj;
if ((this.template == null) ? (other.template != null) : !this.template.equals(other.template)) {
return false;
}
if ((this.mimeType == null) ? (other.mimeType != null) : !this.mimeType.equals(other.mimeType)) {
return false;
}
if (this.maxInstances != other.maxInstances) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + (this.template != null ? this.template.hashCode() : 0);
hash = 89 * hash + (this.mimeType != null ? this.mimeType.hashCode() : 0);
hash = 89 * hash + this.maxInstances;
return hash;
}
}