<NME> AuraResourceServlet.java
<BEF> /*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.auraframework.http;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

import org.auraframework.service.ContextService;
import org.auraframework.system.AuraContext;
import org.auraframework.system.AuraResource;

import com.google.common.collect.ImmutableMap;

/**
 * The aura resource servlet.
 * 
 * This servlet serves up the application content for 'preloaded' definitions. It should be cacheable, which means that
 * the only context used should be the context sent as part of the URL. If any other information is required, caching
 * will cause bugs.
 * 
 * Note that this servlet should be very careful to not attempt to force the client to re-sync (except for manifest
 * fetches), since these calls may well be to re-populate a cache. In general, we should send back at least the basics
 * needed for the client to survive. All resets should be done from {@link AuraServlet}, or when fetching the manifest
 * here.
 */
public class AuraResourceServlet extends AuraBaseServlet {

    private static final long serialVersionUID = -3642790050433142397L;
    public static final String ORIG_REQUEST_URI = "aura.origRequestURI";

    private Map<String, AuraResource> nameToResource;

    private ContextService contextService;

    protected AuraResource findResource(String fullName) {
        if (fullName == null) {
            return null;
        }
        int lIndex = fullName.lastIndexOf("/");
        String last = null;
        int qIndex;

        if (lIndex < fullName.length()) {
/**
 * The aura resource servlet.
 * 
 * This servlet serves up the application content for 'preloaded' definitions. It should be cacheable,
 * which means that the only context used should be the context sent as part of the URL. If any other
 * information is required, caching will cause bugs.
 * 
 * Note that this servlet should be very careful to not attempt to force the client to re-sync (except
 * for manifest fetches), since these calls may well be to re-populate a cache. In general, we should
 * send back at least the basics needed for the client to survive. All resets should be done from {@link AuraServlet},
 * or when fetching the manifest here.
 * 
 * TODO: 'preload': use dependencies instead of namespaces here.
 */
public class AuraResourceServlet extends AuraBaseServlet
{

    private static final String RESOURCE_URLS = "resourceURLs";
    private static final String LAST_MOD = "lastMod";
     */
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding(AuraBaseServlet.UTF_ENCODING);
        AuraContext context = contextService.getCurrentContext();
        AuraResource resource = findResource((String) request.getAttribute(ORIG_REQUEST_URI));
        if (resource == null || context.getApplicationDescriptor() == null) {
            servletUtilAdapter.send404(getServletContext(), request, response);
            return;
        }
        if (servletUtilAdapter.resourceServletGetPre(request, response, resource)) {
    /**
     * A very hackish internal filter.
     * 
     * This is used to apply the theme definition filter for 'templates', which
     * appears to be quite bogus, but is getting rather further embedded in
     * code.
     * 
     * TODO: W-1486762
     */
    public void setContextService(ContextService contextService) {
        this.contextService = contextService;
    }

    private static Map<String, AuraResource> buildNameToResourceMap(final List<AuraResource> auraResources) {
        final Map<String, AuraResource> nameToResource = new HashMap<>(auraResources.size());
        auraResources.stream().filter(r -> r.getName() != null).forEach(r -> {
            final String key = r.getName();
            if (nameToResource.containsKey(key)) {
                throw new IllegalStateException("AuraResource bean (" + r.getClass().getCanonicalName() + ") already registered with this name: '" + key + '\'' );
            }
            nameToResource.put(key, r);
        });
        return ImmutableMap.copyOf(nameToResource);
    }
    
    @Inject
    public void setAuraResources(List<AuraResource> auraResources) {
        this.nameToResource = buildNameToResourceMap(auraResources);
    }
}
    }

    /**
     * Get the set of filters for the current context using a different base
     * component.
     * 
     * Note the special handling here for quick fixes, as we need to be able to
     * get all of the appropriate definitions even when the app fails to
     * compile. In that case we reset everything and get the
     * auradev:quickFixException.
     * 
     * TODO: Note that this means the quickfix handling is hard wired, but I'm
     * not sure that this is an issue. We should maybe make it more obvious by
     * moving things around and using static strings..
     */
    private static List<DescriptorFilter> getFilters() throws QuickFixException {
        AuraContext context = Aura.getContextService().getCurrentContext();
    /**
     * check the top level component/app.
     * 
     * This routine checks to see that we have a valid top level component. If our top level component has some
     * problem (QFE/out of sync) we totally ignore it, and continue with the preloading as if everything was ok.
     * Otherwise, if we have no descriptor, we give back an empty response.
     * 
     * Also note that this handles the 'if-modified-since' header, as we want to tell the browser that nothing
     * changed in that case.
     * 
     * @param request the request (for exception handling)
     * @param response the response (for exception handling)
    /**
     * Write out the manifest.
     * 
     * This writes out the full manifest for an application so that we can use
     * the AppCache.
     * 
     * The manifest contains CSS and JavaScript URLs. These specified resources are copied into the AppCache
     * with the HTML template. When the page is reloaded, the existing manifest is compared to the new manifest.
     * If they are identical, the resources are served from the AppCache. Otherwise,
     * the resources are requested from the server and the AppCache is updated.
     * 
     * @param request the request
     * @param response the response
            // need to make sure that they are in at least one place.
            //
            Map<String, Object> attribs = Maps.newHashMap();
            attribs.put(LAST_MOD, String.format("app=%s, FW=%s", getContextAppUid(),
                    Aura.getConfigAdapter().getAuraFrameworkNonce()));
            attribs.put(UID, getContextAppUid());
            StringWriter sw = new StringWriter();


            DefinitionService definitionService = Aura.getDefinitionService();
            InstanceService instanceService = Aura.getInstanceService();
            DefDescriptor<ComponentDef> tmplDesc = definitionService.getDefDescriptor("ui:manifest",
                    ComponentDef.class);
            Component tmpl = instanceService.getInstance(tmplDesc, attribs);
            Aura.getRenderingService().render(tmpl, response.getWriter());
        } catch (Exception e) {
    /**
     * A cache for compressed CSS output.
     * 
     * This is currently done by namespace, but it will eventually be by app
     * after W-1166679
     */
    private static final Map<String, String> cssCache = new ConcurrentHashMap<String, String>();

    /**
     * write out CSS.
     * 
     * This writes out CSS for the preloads + app to the response. Note that
     * currently it only writes out the preloads because of the missing
     * capability to do the apps will get fixed by W-1166679
     * 
     * @param request the request
     * @param response the response
    /**
     * Write out a set of components in JSON.
     * 
     * FIXME: I have no idea of why this is here when JS does effectively the
     * same thing with extra stuff.
     * 
     * This writes out the entire set of components from the namespaces in JSON.
     */
        Aura.getSerializationService().writeCollection(defs, BaseComponentDef.class, out);
    }

    private static final Map<String, String> definitionCache = new ConcurrentHashMap<String, String>();
    private static final List<DescriptorFilter> aurafilter = Arrays
            .asList(new DescriptorFilter[] { new DescriptorFilter("aura://*:*", "CONTROLLER") });

        }
        key = keyBuilder.toString();

        ret = definitionCache.get(key);
        if (ret != null) {
            out.append(ret);
            return;
            // Note that we just use put (last one wins), as we don't really
            // care what happens
            // when there is a race. Just that one of them gets in.
            definitionCache.put(key, ret);
        }
        else {
            // still store the return in cache in not prod
            definitionCache.put(key, ret);
        }
        out.append(ret);
    }
     * /auraResource?aura.namespaces=&lt;namespace1&gt;/&lt;namespace2&gt;/&lt;namespace3&gt;/...&aura.format=&lt;format&gt;
     * </pre>
     * 
     * Access to this servlet may also follow a shortened URL form specified in
     * aura.conf.
     * 
     * <p>
     * Examples: -

<MSG> To avoid OOM problems, change cached resources to be soft references, so memory
presure (and not merely cache size) can evict them.  The immediate motivation is
that testing auradocs is causing other tests to flake, because of long-lived items
(notably in AuraResourceServlet).  The general problem is that, if we're memory
overloaded, it's better to be slow than to fail.

    @rev goliver@
    @bug W-1561663@

<DFF> @@ -18,6 +18,8 @@ package org.auraframework.http;
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
 import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
@@ -62,19 +64,18 @@ import com.google.common.collect.Sets;
 /**
  * The aura resource servlet.
  * 
- * This servlet serves up the application content for 'preloaded' definitions. It should be cacheable,
- * which means that the only context used should be the context sent as part of the URL. If any other
- * information is required, caching will cause bugs.
+ * This servlet serves up the application content for 'preloaded' definitions. It should be cacheable, which means that
+ * the only context used should be the context sent as part of the URL. If any other information is required, caching
+ * will cause bugs.
  * 
- * Note that this servlet should be very careful to not attempt to force the client to re-sync (except
- * for manifest fetches), since these calls may well be to re-populate a cache. In general, we should
- * send back at least the basics needed for the client to survive. All resets should be done from {@link AuraServlet},
- * or when fetching the manifest here.
+ * Note that this servlet should be very careful to not attempt to force the client to re-sync (except for manifest
+ * fetches), since these calls may well be to re-populate a cache. In general, we should send back at least the basics
+ * needed for the client to survive. All resets should be done from {@link AuraServlet}, or when fetching the manifest
+ * here.
  * 
  * TODO: 'preload': use dependencies instead of namespaces here.
  */
-public class AuraResourceServlet extends AuraBaseServlet
-{
+public class AuraResourceServlet extends AuraBaseServlet {
 
     private static final String RESOURCE_URLS = "resourceURLs";
     private static final String LAST_MOD = "lastMod";
@@ -92,9 +93,8 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * A very hackish internal filter.
      * 
-     * This is used to apply the theme definition filter for 'templates', which
-     * appears to be quite bogus, but is getting rather further embedded in
-     * code.
+     * This is used to apply the theme definition filter for 'templates', which appears to be quite bogus, but is
+     * getting rather further embedded in code.
      * 
      * TODO: W-1486762
      */
@@ -129,17 +129,13 @@ public class AuraResourceServlet extends AuraBaseServlet
     }
 
     /**
-     * Get the set of filters for the current context using a different base
-     * component.
+     * Get the set of filters for the current context using a different base component.
      * 
-     * Note the special handling here for quick fixes, as we need to be able to
-     * get all of the appropriate definitions even when the app fails to
-     * compile. In that case we reset everything and get the
-     * auradev:quickFixException.
+     * Note the special handling here for quick fixes, as we need to be able to get all of the appropriate definitions
+     * even when the app fails to compile. In that case we reset everything and get the auradev:quickFixException.
      * 
-     * TODO: Note that this means the quickfix handling is hard wired, but I'm
-     * not sure that this is an issue. We should maybe make it more obvious by
-     * moving things around and using static strings..
+     * TODO: Note that this means the quickfix handling is hard wired, but I'm not sure that this is an issue. We should
+     * maybe make it more obvious by moving things around and using static strings..
      */
     private static List<DescriptorFilter> getFilters() throws QuickFixException {
         AuraContext context = Aura.getContextService().getCurrentContext();
@@ -167,12 +163,12 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * check the top level component/app.
      * 
-     * This routine checks to see that we have a valid top level component. If our top level component has some
-     * problem (QFE/out of sync) we totally ignore it, and continue with the preloading as if everything was ok.
-     * Otherwise, if we have no descriptor, we give back an empty response.
+     * This routine checks to see that we have a valid top level component. If our top level component has some problem
+     * (QFE/out of sync) we totally ignore it, and continue with the preloading as if everything was ok. Otherwise, if
+     * we have no descriptor, we give back an empty response.
      * 
-     * Also note that this handles the 'if-modified-since' header, as we want to tell the browser that nothing
-     * changed in that case.
+     * Also note that this handles the 'if-modified-since' header, as we want to tell the browser that nothing changed
+     * in that case.
      * 
      * @param request the request (for exception handling)
      * @param response the response (for exception handling)
@@ -226,13 +222,12 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * Write out the manifest.
      * 
-     * This writes out the full manifest for an application so that we can use
-     * the AppCache.
+     * This writes out the full manifest for an application so that we can use the AppCache.
      * 
-     * The manifest contains CSS and JavaScript URLs. These specified resources are copied into the AppCache
-     * with the HTML template. When the page is reloaded, the existing manifest is compared to the new manifest.
-     * If they are identical, the resources are served from the AppCache. Otherwise,
-     * the resources are requested from the server and the AppCache is updated.
+     * The manifest contains CSS and JavaScript URLs. These specified resources are copied into the AppCache with the
+     * HTML template. When the page is reloaded, the existing manifest is compared to the new manifest. If they are
+     * identical, the resources are served from the AppCache. Otherwise, the resources are requested from the server and
+     * the AppCache is updated.
      * 
      * @param request the request
      * @param response the response
@@ -316,8 +311,8 @@ public class AuraResourceServlet extends AuraBaseServlet
             // need to make sure that they are in at least one place.
             //
             Map<String, Object> attribs = Maps.newHashMap();
-            attribs.put(LAST_MOD, String.format("app=%s, FW=%s", getContextAppUid(),
-                    Aura.getConfigAdapter().getAuraFrameworkNonce()));
+            attribs.put(LAST_MOD,
+                    String.format("app=%s, FW=%s", getContextAppUid(), Aura.getConfigAdapter().getAuraFrameworkNonce()));
             attribs.put(UID, getContextAppUid());
             StringWriter sw = new StringWriter();
 
@@ -344,8 +339,8 @@ public class AuraResourceServlet extends AuraBaseServlet
 
             DefinitionService definitionService = Aura.getDefinitionService();
             InstanceService instanceService = Aura.getInstanceService();
-            DefDescriptor<ComponentDef> tmplDesc = definitionService.getDefDescriptor("ui:manifest",
-                    ComponentDef.class);
+            DefDescriptor<ComponentDef> tmplDesc = definitionService
+                    .getDefDescriptor("ui:manifest", ComponentDef.class);
             Component tmpl = instanceService.getInstance(tmplDesc, attribs);
             Aura.getRenderingService().render(tmpl, response.getWriter());
         } catch (Exception e) {
@@ -358,8 +353,7 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * A cache for compressed CSS output.
      * 
-     * This is currently done by namespace, but it will eventually be by app
-     * after W-1166679
+     * This is currently done by namespace, but it will eventually be by app after W-1166679
      */
     private static final Map<String, String> cssCache = new ConcurrentHashMap<String, String>();
 
@@ -375,9 +369,8 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * write out CSS.
      * 
-     * This writes out CSS for the preloads + app to the response. Note that
-     * currently it only writes out the preloads because of the missing
-     * capability to do the apps will get fixed by W-1166679
+     * This writes out CSS for the preloads + app to the response. Note that currently it only writes out the preloads
+     * because of the missing capability to do the apps will get fixed by W-1166679
      * 
      * @param request the request
      * @param response the response
@@ -422,8 +415,7 @@ public class AuraResourceServlet extends AuraBaseServlet
     /**
      * Write out a set of components in JSON.
      * 
-     * FIXME: I have no idea of why this is here when JS does effectively the
-     * same thing with extra stuff.
+     * FIXME: I have no idea of why this is here when JS does effectively the same thing with extra stuff.
      * 
      * This writes out the entire set of components from the namespaces in JSON.
      */
@@ -437,7 +429,8 @@ public class AuraResourceServlet extends AuraBaseServlet
         Aura.getSerializationService().writeCollection(defs, BaseComponentDef.class, out);
     }
 
-    private static final Map<String, String> definitionCache = new ConcurrentHashMap<String, String>();
+    /** Map by URL of soft references to an already-loaded resource. */
+    private static final Map<String, Reference<String>> definitionCache = new ConcurrentHashMap<String, Reference<String>>();
     private static final List<DescriptorFilter> aurafilter = Arrays
             .asList(new DescriptorFilter[] { new DescriptorFilter("aura://*:*", "CONTROLLER") });
 
@@ -471,7 +464,10 @@ public class AuraResourceServlet extends AuraBaseServlet
         }
         key = keyBuilder.toString();
 
-        ret = definitionCache.get(key);
+        Reference<String> reference = definitionCache.get(key);
+        if (reference != null) {
+            ret = reference.get();
+        }
         if (ret != null) {
             out.append(ret);
             return;
@@ -528,11 +524,10 @@ public class AuraResourceServlet extends AuraBaseServlet
             // Note that we just use put (last one wins), as we don't really
             // care what happens
             // when there is a race. Just that one of them gets in.
-            definitionCache.put(key, ret);
-        }
-        else {
+            definitionCache.put(key, new SoftReference<String>(ret));
+        } else {
             // still store the return in cache in not prod
-            definitionCache.put(key, ret);
+            definitionCache.put(key, new SoftReference<String>(ret));
         }
         out.append(ret);
     }
@@ -546,8 +541,7 @@ public class AuraResourceServlet extends AuraBaseServlet
      * /auraResource?aura.namespaces=&lt;namespace1&gt;/&lt;namespace2&gt;/&lt;namespace3&gt;/...&aura.format=&lt;format&gt;
      * </pre>
      * 
-     * Access to this servlet may also follow a shortened URL form specified in
-     * aura.conf.
+     * Access to this servlet may also follow a shortened URL form specified in aura.conf.
      * 
      * <p>
      * Examples: -

{'addition_count': 45, 'commit_subject': "To avoid OOM problems, change cached resources to be soft references, so memory presure (and not merely cache size) can evict them.  The immediate motivation is that testing auradocs is causing other tests to flake, because of long-lived items (notably in AuraResourceServlet).  The general problem is that, if we're memory overloaded, it's better to be slow than to fail.", 'deletion_count': 51, 'file_extension': '.java', 'lang': 'java', 'license': 'apache-2.0', 'repo_name': 'forcedotcom/aura'}