Java tutorial
/* * 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.StringWriter; import java.io.Writer; import java.net.URI; import java.util.Map; import java.util.Set; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHeaders; import org.auraframework.Aura; import org.auraframework.def.ApplicationDef; import org.auraframework.def.BaseComponentDef; import org.auraframework.def.ComponentDef; import org.auraframework.def.DefDescriptor; import org.auraframework.def.DefDescriptor.DefType; import org.auraframework.def.SVGDef; import org.auraframework.http.RequestParam.StringParam; import org.auraframework.instance.Component; import org.auraframework.service.DefinitionService; import org.auraframework.service.InstanceService; import org.auraframework.system.AuraContext; import org.auraframework.system.MasterDefRegistry; import org.auraframework.throwable.ClientOutOfSyncException; import org.auraframework.throwable.quickfix.QuickFixException; import com.google.common.collect.Maps; /** * 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 String RESOURCE_URLS = "resourceURLs"; private static final String LAST_MOD = "lastMod"; private static final String UID = "uid"; private static final long serialVersionUID = -3642790050433142397L; public static final String ORIG_REQUEST_URI = "aura.origRequestURI"; protected static final StringParam lookup = new StringParam(AuraServlet.AURA_PREFIX + "lookup", 0, false); private static ServletContext servletContext; /** * check the top level component/app. * * This routine checks to see that we have a valid top level component. If our top level component is out of sync, * we have to ignore it here, but we _must_ force the client to not cache the response. * * If there is a QFE, we substitute the QFE descriptor for the one given us, and continue. Again, we cannot allow * caching. * * Finally, if there is no descriptor given, we simply ignore the request and give them an empty response. Which is * done here by returning null. * * 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) * @param context the context to get the definition. * @return the set of descriptors we are sending back, or null in the case that we handled the response. * @throws IOException if there was an IO exception handling a client out of sync exception * @throws ServletException if there was a problem handling the out of sync */ private Set<DefDescriptor<?>> handleTopLevel(HttpServletRequest request, HttpServletResponse response, AuraContext context) throws IOException, ServletException { DefDescriptor<? extends BaseComponentDef> appDesc = context.getApplicationDescriptor(); DefinitionService definitionService = Aura.getDefinitionService(); MasterDefRegistry mdr = context.getDefRegistry(); context.setPreloading(true); if (appDesc == null) { // // This means we have nothing to say to the client, so the response is // left completely empty. // return null; } long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); String uid = context.getUid(appDesc); try { try { definitionService.updateLoaded(appDesc); if (uid != null && ifModifiedSince != -1) { // // In this case, we have an unmodified descriptor, so just tell // the client that. // response.sendError(HttpServletResponse.SC_NOT_MODIFIED); return null; } } catch (ClientOutOfSyncException coose) { // // We can't actually handle an out of sync here, since we are doing a // preload. We have to ignore it, and continue as if nothing happened. // But in the process, we make sure to set 'no-cache' so that the result // is thrown away. This may actually not give the right result in bizarre // corner cases... beware cache inconsistencied on revert after a QFE. // // We actually probably should do something different, like send a minimalist // set of stuff to make the client re-try. // setNoCache(response); String oosUid = mdr.getUid(null, appDesc); return mdr.getDependencies(oosUid); } } catch (QuickFixException qfe) { DefDescriptor<ComponentDef> qfeDescriptor; // // A quickfix exception means that we couldn't compile something. // In this case, we still want to preload things, but we want to preload // quick fix values, note that we force NoCache here. // setNoCache(response); qfeDescriptor = definitionService.getDefDescriptor("markup://auradev:quickFixException", ComponentDef.class); context.setLoadingApplicationDescriptor(qfeDescriptor); String qfeUid; try { qfeUid = mdr.getUid(null, qfeDescriptor); } catch (QuickFixException death) { // // Ok, we really can't handle this here, so just punt. This means that // the quickfix display is broken, and whatever we try will give us grief. // response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } return mdr.getDependencies(qfeUid); } setLongCache(response); if (uid == null) { uid = context.getUid(appDesc); } return mdr.getDependencies(uid); } /** * 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 * @throws IOException if unable to write out the response */ private void writeManifest(HttpServletRequest request, HttpServletResponse response) throws IOException { AuraContext context = Aura.getContextService().getCurrentContext(); setNoCache(response); try { // // First, we make sure that the manifest is enabled. // if (!ManifestUtil.isManifestEnabled(request)) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } // // Now we validate the cookie, which includes loop detection. // this routine sets the response code. // if (!ManifestUtil.checkManifestCookie(request, response)) { return; } boolean appOk = false; DefDescriptor<? extends BaseComponentDef> descr = null; try { descr = context.getApplicationDescriptor(); if (descr != null) { Aura.getDefinitionService().updateLoaded(descr); appOk = true; } } catch (QuickFixException qfe) { // // ignore qfe, since we really don't care... the manifest will be 404ed. // This will eventually cause the browser to give up. Note that this case // should almost never occur, as it requires the qfe to be introduced between // the initial request (which will not set a manifest if it gets a qfe) and // the manifest request. // } catch (ClientOutOfSyncException coose) { // // In this case, we want to force a reload... A 404 on the manifest is // supposed to handle this. we hope that the client will do the right // thing, and reload everything. Note that this case really should only // happen if the client already has content, and thus should be refreshing // However, there are very odd edge cases that we probably can't detect // without keeping server side state, such as the case that something // is updated between the initial HTML request and the manifest request. // Not sure what browsers will do in this case. // } if (!appOk) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } // // This writes both the app and framework signatures into // the manifest, so that if either one changes, the // manifest will change. Note that in most cases, we will // write these signatures in multiple places, but we just // need to make sure that they are in at least one place. // Map<String, Object> attribs = Maps.newHashMap(); String appUid = getContextAppUid(); attribs.put(LAST_MOD, String.format("app=%s, FW=%s", appUid, Aura.getConfigAdapter().getAuraFrameworkNonce())); attribs.put(UID, appUid); StringWriter sw = new StringWriter(); for (String s : getStyles()) { sw.write(s); sw.write('\n'); } for (String s : getScripts()) { sw.write(s); sw.write('\n'); } // Add in any application specific resources if (descr != null && descr.getDefType().equals(DefType.APPLICATION)) { ApplicationDef def = (ApplicationDef) descr.getDef(); for (String s : def.getAdditionalAppCacheURLs()) { sw.write(s); sw.write('\n'); } } attribs.put(RESOURCE_URLS, sw.toString()); 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) { Aura.getExceptionAdapter().handleException(e); // Can't throw exception here: to set manifest OBSOLETE response.setStatus(HttpServletResponse.SC_NOT_FOUND); } } private void writeCss(HttpServletRequest request, Set<DefDescriptor<?>> dependencies, AuraContext context, Writer out) throws IOException, QuickFixException { if (isAppRequest(request)) { Aura.getServerService().writeAppCss(dependencies, out); } else { Aura.getClientLibraryService().writeCss(context, out); } } private boolean isAppRequest(HttpServletRequest request) { String type = request.getParameter(AuraResourceRewriteFilter.TYPE_PARAM); if (StringUtils.endsWithIgnoreCase(type, "app")) { return true; } return false; } private void writeJs(HttpServletRequest request, Set<DefDescriptor<?>> dependencies, AuraContext context, Writer out) throws IOException, QuickFixException { if (isAppRequest(request)) { Aura.getServerService().writeDefinitions(dependencies, out); } else { Aura.getClientLibraryService().writeJs(context, out); } } private void writeSvg(HttpServletRequest request, AuraContext context, Writer out) throws IOException, QuickFixException { String fqn = lookup.get(request); if (fqn == null || fqn.isEmpty()) { fqn = context.getApplicationDescriptor().getQualifiedName(); } DefDescriptor<SVGDef> svg = Aura.getDefinitionService().getDefDescriptor(fqn, SVGDef.class); Aura.getServerService().writeAppSvg(svg, out); } /** * Serves up CSS or JS resources for a list of namespaces. * * URLs follow the format: * * <pre> * /auraResource?aura.namespaces=<namespace1>/<namespace2>/<namespace3>/...&aura.format=<format> * </pre> * * Access to this servlet may also follow a shortened URL form specified in aura.conf. * * <p> * Examples: - * * <pre> * /l/123123123/aura/os/mobile.css * </pre> * * (The number is the last mod timestamp) - * * <pre> * /l/213423423/aura/os.js * </pre> * * - * * <pre> * /l/aura/os/mobile.css * </pre> * * - * * <pre> * /l/aura/os.js * </pre> * * </p> * * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AuraContext context = Aura.getContextService().getCurrentContext(); Set<DefDescriptor<?>> topLevel; response.setCharacterEncoding(AuraBaseServlet.UTF_ENCODING); setLongCache(response); AuraContext.Format format = context.getFormat(); response.setContentType(getContentType(format)); setBasicHeaders(context.getApplicationDescriptor(), request, response); switch (format) { case MANIFEST: writeManifest(request, response); break; case CSS: topLevel = handleTopLevel(request, response, context); if (topLevel == null) { return; } try { writeCss(request, topLevel, context, response.getWriter()); } catch (Throwable t) { handleServletException(t, true, context, request, response, true); } break; case JS: topLevel = handleTopLevel(request, response, context); if (topLevel == null) { return; } try { writeJs(request, topLevel, context, response.getWriter()); } catch (Throwable t) { handleServletException(t, true, context, request, response, true); } break; case JSON: try { Aura.getConfigAdapter().validateCSRFToken(csrfToken.get(request)); } catch (Throwable t) { handleServletException(t, true, context, request, response, false); return; } topLevel = handleTopLevel(request, response, context); if (topLevel == null) { return; } try { Aura.getServerService().writeComponents(topLevel, response.getWriter()); } catch (Throwable t) { handleServletException(t, true, context, request, response, true); } break; case SVG: try { writeSvg(request, context, response.getWriter()); } catch (Throwable t) { handleServletException(t, true, context, request, response, true); } break; default: break; } } protected boolean checkAccess(DefDescriptor<?> desc) { return true; } public static boolean isResourceLocallyAvailable(String resourceURI) { if (resourceURI != null && resourceURI.startsWith("/") && servletContext != null) { try { URI uri = URI.create(resourceURI); if (uri != null) { ServletContext c = servletContext.getContext(uri.getPath()); if (c != null && c.getResource(uri.getPath()) != null) { return true; } } } catch (Exception e) { } } return false; } @Override public void init(ServletConfig config) { servletContext = config.getServletContext(); } }