/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.appclient.server.core.jws.servedcontent; import java.util.LinkedList; import java.util.Properties; import org.glassfish.appclient.server.core.jws.Util; import org.glassfish.appclient.server.core.jws.servedcontent.DynamicContent.InstanceAdapter; /** * * @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; } }