Java tutorial
/* * (C) Copyright 2012, IBM Corporation * * 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 com.ibm.jaggr.service.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.StringReader; import java.net.URI; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleException; import org.osgi.framework.BundleListener; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; import com.ibm.jaggr.service.BadRequestException; import com.ibm.jaggr.service.DependencyVerificationException; import com.ibm.jaggr.service.IAggregator; import com.ibm.jaggr.service.IAggregatorExtension; import com.ibm.jaggr.service.IExtensionInitializer; import com.ibm.jaggr.service.IExtensionInitializer.IExtensionRegistrar; import com.ibm.jaggr.service.IRequestListener; import com.ibm.jaggr.service.IShutdownListener; import com.ibm.jaggr.service.IVariableResolver; import com.ibm.jaggr.service.InitParams; import com.ibm.jaggr.service.InitParams.InitParam; import com.ibm.jaggr.service.NotFoundException; import com.ibm.jaggr.service.ProcessingDependenciesException; import com.ibm.jaggr.service.cache.ICacheManager; import com.ibm.jaggr.service.config.IConfig; import com.ibm.jaggr.service.config.IConfigListener; import com.ibm.jaggr.service.deps.IDependencies; import com.ibm.jaggr.service.executors.IExecutors; import com.ibm.jaggr.service.impl.cache.CacheManagerImpl; import com.ibm.jaggr.service.impl.config.ConfigImpl; import com.ibm.jaggr.service.impl.deps.DependenciesImpl; import com.ibm.jaggr.service.impl.layer.LayerImpl; import com.ibm.jaggr.service.impl.module.ModuleImpl; import com.ibm.jaggr.service.impl.options.OptionsImpl; import com.ibm.jaggr.service.layer.ILayer; import com.ibm.jaggr.service.layer.ILayerCache; import com.ibm.jaggr.service.module.IModule; import com.ibm.jaggr.service.module.IModuleCache; import com.ibm.jaggr.service.modulebuilder.IModuleBuilder; import com.ibm.jaggr.service.modulebuilder.IModuleBuilderExtensionPoint; import com.ibm.jaggr.service.options.IOptions; import com.ibm.jaggr.service.options.IOptionsListener; import com.ibm.jaggr.service.resource.IResource; import com.ibm.jaggr.service.resource.IResourceFactory; import com.ibm.jaggr.service.resource.IResourceFactoryExtensionPoint; import com.ibm.jaggr.service.transport.IHttpTransport; import com.ibm.jaggr.service.transport.IHttpTransportExtensionPoint; import com.ibm.jaggr.service.util.CopyUtil; import com.ibm.jaggr.service.util.SequenceNumberProvider; import com.ibm.jaggr.service.util.StringUtil; /** * Implementation for IAggregator and HttpServlet interfaces. * * Note that despite the fact that HttpServlet (which this class extends) * implements Serializable, attempts to serialize instances of this class will * fail due to the fact that not all instance data is serializable. The * assumption is that because instances of this class are created by the OSGi * Framework, and the framework itself does not support serialization, then no * attempts will be made to serialize instances of this class. */ @SuppressWarnings("serial") public class AggregatorImpl extends HttpServlet implements IExecutableExtension, IOptionsListener, BundleListener, IAggregator { /** * Default value for resourcefactories init-param */ protected static final String DEFAULT_RESOURCEFACTORIES = "com.ibm.jaggr.service.default.resourcefactories"; //$NON-NLS-1$ /** * Default value for modulebuilders init-param */ protected static final String DEFAULT_MODULEBUILDERS = "com.ibm.jaggr.service.default.modulebuilders"; //$NON-NLS-1$ /** * Default value for httptransport init-param */ protected static final String DEFAULT_HTTPTRANSPORT = "com.ibm.jaggr.service.dojo.httptransport"; //$NON-NLS-1$ private static final Logger log = Logger.getLogger(AggregatorImpl.class.getName()); private File workdir = null; private ICacheManager cacheMgr = null; private IConfig config = null; private ServiceTracker optionsServiceTracker = null; private ServiceTracker executorsServiceTracker = null; private ServiceTracker variableResolverServiceTracker = null; private Bundle bundle = null; private String name = null; private IDependencies deps = null; private List<ServiceRegistration> registrations = new LinkedList<ServiceRegistration>(); private List<ServiceReference> serviceReferences = Collections .synchronizedList(new LinkedList<ServiceReference>()); private InitParams initParams = null; private IOptions localOptions = null; private LinkedList<IAggregatorExtension> resourceFactoryExtensions = new LinkedList<IAggregatorExtension>(); private LinkedList<IAggregatorExtension> moduleBuilderExtensions = new LinkedList<IAggregatorExtension>(); private IAggregatorExtension httpTransportExtension = null; enum RequestNotifierAction { start, end }; /* (non-Javadoc) * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig) */ @Override public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); final ServletContext context = servletConfig.getServletContext(); // Set servlet context attributes for access though the request context.setAttribute(IAggregator.AGGREGATOR_REQATTRNAME, this); } /* (non-Javadoc) * @see javax.servlet.GenericServlet#destroy() */ @Override public void destroy() { shutdown(); // Clear references to objects that can potentially reference this object // so as to avoid memory leaks due to circular references. resourceFactoryExtensions.clear(); moduleBuilderExtensions.clear(); httpTransportExtension = null; cacheMgr = null; config = null; deps = null; super.destroy(); } /** * Called when the aggregator is shutting down. Note that there is inconsistency * among servlet bridge implementations over when {@link HttpServlet#destroy()} * is called relative to when (or even if) the bundle is stopped. So this method * may be called from the destroy method or the bundle listener or both. */ synchronized protected void shutdown() { // Make sure the bundle context is valid int state = bundle != null ? bundle.getState() : Bundle.RESOLVED; if (state == Bundle.ACTIVE || state == Bundle.STOPPING) { BundleContext bundleContext = getBundleContext(); bundle = null; // make sure we don't shutdown more than once ServiceReference[] refs = null; try { refs = bundleContext.getServiceReferences(IShutdownListener.class.getName(), "(name=" + getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (InvalidSyntaxException e) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e.getMessage(), e); } } if (refs != null) { for (ServiceReference ref : refs) { IShutdownListener listener = (IShutdownListener) bundleContext.getService(ref); if (listener != null) { try { listener.shutdown(this); } catch (Exception e) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e.getMessage(), e); } } finally { bundleContext.ungetService(ref); } } } } for (ServiceRegistration registration : registrations) { registration.unregister(); } for (ServiceReference ref : serviceReferences) { bundleContext.ungetService(ref); } bundleContext.removeBundleListener(this); optionsServiceTracker.close(); executorsServiceTracker.close(); variableResolverServiceTracker.close(); } } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { if (log.isLoggable(Level.FINEST)) log.finest("doGet: URL=" + req.getRequestURI()); //$NON-NLS-1$ req.setAttribute(AGGREGATOR_REQATTRNAME, this); ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<String, Object>(); req.setAttribute(CONCURRENTMAP_REQATTRNAME, concurrentMap); try { // Validate config last-modified if development mode is enabled if (getOptions().isDevelopmentMode()) { long lastModified = -1; URI configUri = getConfig().getConfigUri(); if (configUri != null) { lastModified = configUri.toURL().openConnection().getLastModified(); } if (lastModified > getConfig().lastModified()) { if (reloadConfig()) { // If the config has been modified, then dependencies will be revalidated // asynchronously. Rather than forcing the current request to wait, return // a response that will display an alert informing the user of what is // happening and asking them to reload the page. String content = "alert('" + //$NON-NLS-1$ StringUtil.escapeForJavaScript(Messages.ConfigModified) + "');"; //$NON-NLS-1$ resp.addHeader("Cache-control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$ CopyUtil.copy(new StringReader(content), resp.getOutputStream()); return; } } } getTransport().decorateRequest(req); notifyRequestListeners(RequestNotifierAction.start, req, resp); ILayer layer = getLayer(req); long modifiedSince = req.getDateHeader("If-Modified-Since"); //$NON-NLS-1$ long lastModified = (Math.max(getCacheManager().getCache().getCreated(), layer.getLastModified(req)) / 1000) * 1000; if (modifiedSince >= lastModified) { if (log.isLoggable(Level.FINER)) { log.finer("Returning Not Modified response for layer in servlet" + //$NON-NLS-1$ getName() + ":" //$NON-NLS-1$ + req.getAttribute(IHttpTransport.REQUESTEDMODULES_REQATTRNAME).toString()); } resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } else { // Get the InputStream for the response. This call sets the Content-Type, // Content-Length and Content-Encoding headers in the response. InputStream in = layer.getInputStream(req, resp); // if any of the readers included an error response, then don't cache the layer. if (req.getAttribute(ILayer.NOCACHE_RESPONSE_REQATTRNAME) != null) { resp.addHeader("Cache-Control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$ } else { resp.setDateHeader("Last-Modified", lastModified); //$NON-NLS-1$ int expires = getConfig().getExpires(); if (expires > 0) { resp.addHeader("Cache-Control", "max-age=" + expires); //$NON-NLS-1$ //$NON-NLS-2$ } } CopyUtil.copy(in, resp.getOutputStream()); } notifyRequestListeners(RequestNotifierAction.end, req, resp); } catch (DependencyVerificationException e) { // clear the cache now even though it will be cleared when validateDeps has // finished (asynchronously) so that any new requests will be forced to wait // until dependencies have been validated. getCacheManager().clearCache(); getDependencies().validateDeps(false); resp.addHeader("Cache-control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$ if (getOptions().isDevelopmentMode()) { String msg = StringUtil.escapeForJavaScript(MessageFormat.format(Messages.DepVerificationFailed, new Object[] { e.getMessage(), AggregatorCommandProvider.EYECATCHER + " " + //$NON-NLS-1$ AggregatorCommandProvider.CMD_VALIDATEDEPS + " " + //$NON-NLS-1$ getName() + " " + //$NON-NLS-1$ AggregatorCommandProvider.PARAM_CLEAN, getWorkingDirectory().toString().replace("\\", "\\\\") //$NON-NLS-1$ //$NON-NLS-2$ })); String content = "alert('" + msg + "');"; //$NON-NLS-1$ //$NON-NLS-2$ try { CopyUtil.copy(new StringReader(content), resp.getOutputStream()); } catch (IOException e1) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e1.getMessage(), e1); } resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } else { resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); } } catch (ProcessingDependenciesException e) { resp.addHeader("Cache-control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$ if (getOptions().isDevelopmentMode()) { String content = "alert('" + StringUtil.escapeForJavaScript(Messages.Busy) + "');"; //$NON-NLS-1$ //$NON-NLS-2$ try { CopyUtil.copy(new StringReader(content), resp.getOutputStream()); } catch (IOException e1) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e1.getMessage(), e1); } resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } else { resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); } } catch (BadRequestException e) { exceptionResponse(req, resp, e, HttpServletResponse.SC_BAD_REQUEST); } catch (NotFoundException e) { exceptionResponse(req, resp, e, HttpServletResponse.SC_NOT_FOUND); } catch (Exception e) { exceptionResponse(req, resp, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { concurrentMap.clear(); } } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#reloadConfig() */ @Override public boolean reloadConfig() throws IOException { return loadConfig(SequenceNumberProvider.incrementAndGetSequenceNumber()); } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getDependencies() */ @Override public IDependencies getDependencies() { return deps; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getName() */ @Override public String getName() { return name; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getOptions() */ @Override public IOptions getOptions() { return (localOptions != null) ? localOptions : (IOptions) optionsServiceTracker.getService(); } @Override public IExecutors getExecutors() { return (IExecutors) executorsServiceTracker.getService(); } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getConfig() */ @Override public IConfig getConfig() { return config; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getCacheManager() */ @Override public ICacheManager getCacheManager() { return cacheMgr; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getBundleContext() */ @Override public BundleContext getBundleContext() { return bundle != null ? bundle.getBundleContext() : null; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getInitParams() */ @Override public InitParams getInitParams() { return initParams; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getWorkingDirectory() */ @Override public File getWorkingDirectory() { return workdir; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#asServlet() */ @Override public HttpServlet asServlet() { return this; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getTransport() */ @Override public IHttpTransport getTransport() { return (IHttpTransport) getHttpTransportExtension().getInstance(); } /* (non-Javadoc) * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object) */ @Override public void setInitializationData(IConfigurationElement configElem, String propertyName, Object data) throws CoreException { Bundle contributingBundle = Platform.getBundle(configElem.getNamespaceIdentifier()); if (contributingBundle.getState() != Bundle.ACTIVE) { try { contributingBundle.start(); } catch (BundleException e) { throw new CoreException( new Status(Status.ERROR, configElem.getNamespaceIdentifier(), e.getMessage(), e)); } } try { BundleContext bundleContext = contributingBundle.getBundleContext(); bundle = bundleContext.getBundle(); name = getAggregatorName(configElem); initParams = getInitParams(configElem); executorsServiceTracker = getExecutorsServiceTracker(bundleContext); variableResolverServiceTracker = getVariableResolverServiceTracker(bundleContext); initOptions(initParams); workdir = initWorkingDirectory( // this must be after initOptions Platform.getStateLocation(getBundleContext().getBundle()).toFile(), configElem); initExtensions(configElem); // create the config. Keep it local so it won't be seen by deps and cacheMgr // until after we check for customization last-mods. Then we'll set the config // in the instance data and call the config listeners. IConfig config = newConfig(); // Check last-modified times of resources in the overrides folders. These resources // are considered to be dynamic in a production environment and we want to // detect new/changed resources in these folders on startup so that we can clear // caches, etc. OverrideFoldersTreeWalker walker = new OverrideFoldersTreeWalker(this, config); walker.walkTree(); deps = newDependencies(walker.getLastModifiedJS()); cacheMgr = newCacheManager(walker.getLastModified()); this.config = config; // Notify listeners notifyConfigListeners(1); bundleContext.addBundleListener(this); } catch (Exception e) { throw new CoreException( new Status(Status.ERROR, configElem.getNamespaceIdentifier(), e.getMessage(), e)); } Properties dict = new Properties(); dict.put("name", getName()); //$NON-NLS-1$ registrations.add(getBundleContext().registerService(IAggregator.class.getName(), this, dict)); } /* (non-Javadoc) * @see com.ibm.jaggr.service.resource.IResourceProvider#getResource(java.net.URI) */ @Override public IResource newResource(URI uri) { IResourceFactory factory = null; String scheme = uri.getScheme(); for (IAggregatorExtension extension : getResourceFactoryExtensions()) { if (scheme.equals(extension.getAttribute(IResourceFactoryExtensionPoint.SCHEME_ATTRIBUTE))) { IResourceFactory test = (IResourceFactory) extension.getInstance(); if (test.handles(uri)) { factory = test; break; } } } if (factory == null) { throw new UnsupportedOperationException("No resource factory for " + uri.toString() //$NON-NLS-1$ ); } return factory.newResource(uri); } /* (non-Javadoc) * @see com.ibm.jaggr.service.modulebuilder.IModuleBuilderProvider#getModuleBuilder(java.lang.String, com.ibm.jaggr.service.resource.IResource) */ @Override public IModuleBuilder getModuleBuilder(String mid, IResource res) { IModuleBuilder builder = null; String path = res.getURI().getPath(); int idx = path.lastIndexOf("."); //$NON-NLS-1$ String ext = (idx == -1) ? "" : path.substring(idx + 1); //$NON-NLS-1$ if (ext.contains("/")) { //$NON-NLS-1$ ext = ""; //$NON-NLS-1$ } for (IAggregatorExtension extension : getModuleBuilderExtensions()) { String extAttrib = extension.getAttribute(IModuleBuilderExtensionPoint.EXTENSION_ATTRIBUTE); if (ext.equals(extAttrib) || "*".equals(extAttrib)) { //$NON-NLS-1$ IModuleBuilder test = (IModuleBuilder) extension.getInstance(); if (test.handles(mid, res)) { builder = test; break; } } } if (builder == null) { throw new UnsupportedOperationException("No module builder for " + mid //$NON-NLS-1$ ); } return builder; } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getResourceFactoryExtensions() */ @Override public Iterable<IAggregatorExtension> getResourceFactoryExtensions() { return Collections.unmodifiableList(resourceFactoryExtensions); } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getModuleBuilderExtensions() */ @Override public Iterable<IAggregatorExtension> getModuleBuilderExtensions() { return Collections.unmodifiableList(moduleBuilderExtensions); } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#getHttpTransportExtension() */ @Override public IAggregatorExtension getHttpTransportExtension() { return httpTransportExtension; } /* (non-Javadoc) * @see com.ibm.jaggr.service.module.IModuleFactory#newModule(java.lang.String, java.net.URI) */ @Override public IModule newModule(String mid, URI uri) { return new ModuleImpl(mid, uri); } /* (non-Javadoc) * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent) */ @Override public void bundleChanged(BundleEvent event) { if (event.getType() == BundleEvent.STOPPING) { shutdown(); } } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#newLayerCache() */ @Override public ILayerCache newLayerCache() { return LayerImpl.newLayerCache(this); } /* (non-Javadoc) * @see com.ibm.jaggr.service.IAggregator#newModuleCache() */ @Override public IModuleCache newModuleCache() { return ModuleImpl.newModuleCache(this); } /** * Options update listener forwarder. This listener is registered under the option's * name (the name of the servlet's bundle), and forwards listener events to listeners * that are registered under the aggregator name. * * @param options The options object * @param sequence The event sequence number */ @Override public void optionsUpdated(IOptions options, long sequence) { // Options have been updated. Notify any listeners that registered using this // aggregator instance's name. ServiceReference[] refs = null; try { refs = getBundleContext().getServiceReferences(IOptionsListener.class.getName(), "(name=" + getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ if (refs != null) { for (ServiceReference ref : refs) { IOptionsListener listener = (IOptionsListener) getBundleContext().getService(ref); if (listener != null) { try { listener.optionsUpdated(options, sequence); } catch (Throwable ignore) { } finally { getBundleContext().ungetService(ref); } } } } } catch (InvalidSyntaxException e) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e.getMessage(), e); } } } /** * Returns the name for the bundle containing the servlet code. This is used * to look up services like IOptions and IExecutors that are registered by the * bundle activator. * * @return The servlet bundle name. */ protected String getServletBundleName() { return Activator.BUNDLE_NAME; } /** * Sets response status and headers for an error response based on the * information in the specified exception. If development mode is * enabled, then returns a 200 status with a console.error() message * specifying the exception message * * @param resp The response object * @param t The exception object * @param status The response status */ protected void exceptionResponse(HttpServletRequest req, HttpServletResponse resp, Throwable t, int status) { resp.addHeader("Cache-control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$ Level logLevel = (t instanceof BadRequestException || t instanceof NotFoundException) ? Level.WARNING : Level.SEVERE; if (log.isLoggable(logLevel)) { String queryArgs = req.getQueryString(); StringBuffer url = req.getRequestURL(); if (queryArgs != null) { url.append("?").append(queryArgs); //$NON-NLS-1$ } log.log(logLevel, url.toString(), t); } if (getOptions().isDevelopmentMode() || getOptions().isDebugMode()) { // In development mode, display server exceptions on the browser console String msg = StringUtil.escapeForJavaScript( MessageFormat.format(Messages.ExceptionResponse, new Object[] { t.getClass().getName(), t.getMessage() != null ? StringUtil.escapeForJavaScript(t.getMessage()) : "" //$NON-NLS-1$ })); String content = "console.error('" + msg + "');"; //$NON-NLS-1$ //$NON-NLS-2$ try { CopyUtil.copy(new StringReader(content), resp.getOutputStream()); } catch (IOException e1) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, e1.getMessage(), e1); } resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } else { resp.setStatus(status); } } /** * Returns the {@code Layer} object for the specified request. * * @param request The request object * @return The layer for the request * @throws Exception */ protected ILayer getLayer(HttpServletRequest request) throws Exception { // Try non-blocking get() request first return getCacheManager().getCache().getLayers().getLayer(request); } /* (non-Javadoc) * @see com.ibm.servlets.amd.aggregator.IAggregator#substituteProps(java.lang.String) */ @Override public String substituteProps(String str) { return substituteProps(str, null); } private final Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}"); //$NON-NLS-1$ /* (non-Javadoc) * @see com.ibm.servlets.amd.aggregator.IAggregator#substituteProps(java.lang.String, com.ibm.servlets.amd.aggregator.IAggregator.SubstitutionTransformer) */ @Override public String substituteProps(String str, SubstitutionTransformer transformer) { if (str == null) { return null; } StringBuffer buf = new StringBuffer(); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String propName = matcher.group(1); String propValue = null; if (getBundleContext() != null) { propValue = getBundleContext().getProperty(propName); } else { propValue = System.getProperty(propName); } if (propValue == null && variableResolverServiceTracker != null) { ServiceReference[] refs = variableResolverServiceTracker.getServiceReferences(); if (refs != null) { for (ServiceReference sr : refs) { IVariableResolver resolver = (IVariableResolver) getBundleContext().getService(sr); try { propValue = resolver.resolve(propName); if (propValue != null) { break; } } finally { getBundleContext().ungetService(sr); } } } } if (propValue != null) { if (transformer != null) { propValue = transformer.transform(propName, propValue); } matcher.appendReplacement(buf, propValue.replace("\\", "\\\\") //$NON-NLS-1$ //$NON-NLS-2$ .replace("$", "\\$") //$NON-NLS-1$ //$NON-NLS-2$ ); } else { matcher.appendReplacement(buf, "\\${" + propName + "}"); //$NON-NLS-1$ //$NON-NLS-2$ } } matcher.appendTail(buf); return buf.toString(); } /** * Loads the {@code IConfig} for this aggregator * * @param seq The config change sequence number * @return True if the new config is changed from the old config * @throws IOException */ protected boolean loadConfig(long seq) throws IOException { boolean modified = false; try { Object previousConfig = config != null ? config.toString() : null; config = newConfig(); modified = previousConfig != null && !previousConfig.equals(config.toString()); } catch (IOException e) { throw new IOException(e); } notifyConfigListeners(seq); return modified; } /** * Calls the registered request notifier listeners. * * @param action The request action (start or end) * @param req The request object. * @param resp The response object. * @throws IOException */ protected void notifyRequestListeners(RequestNotifierAction action, HttpServletRequest req, HttpServletResponse resp) throws IOException { // notify any listeners that the config has been updated ServiceReference[] refs = null; try { refs = getBundleContext().getServiceReferences(IRequestListener.class.getName(), "(name=" + getName() + ")" //$NON-NLS-1$ //$NON-NLS-2$ ); } catch (InvalidSyntaxException e) { throw new IOException(e); } for (ServiceReference ref : refs) { IRequestListener listener = (IRequestListener) getBundleContext().getService(ref); try { if (action == RequestNotifierAction.start) { listener.startRequest(req, resp); } else { listener.endRequest(req, resp); } } finally { getBundleContext().ungetService(ref); } } } /** * Call the registered config change listeners * * @param seq The change listener sequence number * @throws IOException */ protected void notifyConfigListeners(long seq) throws IOException { ServiceReference[] refs; try { // notify any listeners that the config has been updated refs = getBundleContext().getServiceReferences(IConfigListener.class.getName(), "(name=" + getName() + ")" //$NON-NLS-1$ //$NON-NLS-2$ ); } catch (InvalidSyntaxException e) { throw new IOException(e); } if (refs != null) { for (ServiceReference ref : refs) { IConfigListener listener = (IConfigListener) getBundleContext().getService(ref); if (listener != null) { try { listener.configLoaded(config, seq); } catch (Throwable t) { if (log.isLoggable(Level.SEVERE)) { log.log(Level.SEVERE, t.getMessage(), t); } throw new IOException(t); } finally { getBundleContext().ungetService(ref); } } } } } /** * Adds the specified resource factory extension to the list of registered * resource factory extensions. * * @param loadedExt * The extension to add * @param before * Reference to an existing resource factory extension that the * new extension should be placed before in the list. If null, * then the new extension is added to the end of the list */ protected void registerResourceFactory(IAggregatorExtension loadedExt, IAggregatorExtension before) { if (!(loadedExt.getInstance() instanceof IResourceFactory)) { throw new IllegalArgumentException(loadedExt.getInstance().getClass().getName()); } if (before == null) { resourceFactoryExtensions.add(loadedExt); } else { // find the extension to insert the item in front of boolean inserted = false; for (int i = 0; i < resourceFactoryExtensions.size(); i++) { if (resourceFactoryExtensions.get(i) == before) { resourceFactoryExtensions.add(i, loadedExt); inserted = true; break; } } if (!inserted) { throw new IllegalArgumentException(); } } } /** * Adds the specified module builder extension to the list of registered * module builder extensions. * * @param loadedExt * The extension to add * @param before * Reference to an existing module builder extension that the * new extension should be placed before in the list. If null, * then the new extension is added to the end of the list */ protected void registerModuleBuilder(IAggregatorExtension loadedExt, IAggregatorExtension before) { if (!(loadedExt.getInstance() instanceof IModuleBuilder)) { throw new IllegalArgumentException(loadedExt.getInstance().getClass().getName()); } if (before == null) { moduleBuilderExtensions.add(loadedExt); } else { // find the extension to insert the item in front of boolean inserted = false; for (int i = 0; i < moduleBuilderExtensions.size(); i++) { if (moduleBuilderExtensions.get(i) == before) { moduleBuilderExtensions.add(i, loadedExt); inserted = true; break; } } if (!inserted) { throw new IllegalArgumentException(); } } } /** * Registers the specified http transport extension as the * http transport for this aggregator. * * @param loadedExt The extension to register */ protected void registerHttpTransport(IAggregatorExtension loadedExt) { if (!(loadedExt.getInstance() instanceof IHttpTransport)) { throw new IllegalArgumentException(loadedExt.getInstance().getClass().getName()); } httpTransportExtension = loadedExt; } /** * For each extension specified, call the extension's * {@link IExtensionInitializer#initialize} method. Note that this * can cause additional extensions to be registered though the * {@link ExtensionRegistrar}. * * @param extensions The list of extensions to initialize * @param reg The extension registrar. */ protected void callExtensionInitializers(Iterable<IAggregatorExtension> extensions, ExtensionRegistrar reg) { for (IAggregatorExtension extension : extensions) { Object instance = extension.getInstance(); if (instance instanceof IExtensionInitializer) { ((IExtensionInitializer) instance).initialize(this, extension, reg); } } } protected void initOptions(InitParams initParams) throws InvalidSyntaxException { optionsServiceTracker = getOptionsServiceTracker(getBundleContext()); String registrationName = getServletBundleName(); List<String> values = initParams.getValues(InitParams.OPTIONS_INITPARAM); if (values != null && values.size() > 0) { String value = values.get(0); final File file = new File(substituteProps(value)); if (file.exists()) { registrationName = registrationName + ":" + getName(); //$NON-NLS-1$ localOptions = new OptionsImpl(getBundleContext(), registrationName, true) { @Override public File getPropsFile() { return file; } }; if (log.isLoggable(Level.INFO)) { log.info(MessageFormat.format(Messages.CustomOptionsFile, new Object[] { file.toString() })); } } } Properties dict = new Properties(); dict.put("name", registrationName); //$NON-NLS-1$ registrations.add(getBundleContext().registerService(IOptionsListener.class.getName(), this, dict)); } /** * Loads and initializes the resource factory, module builder and * http transport extensions specified in the configuration * element for this aggregator * * @param configElem The configuration element * @throws CoreException * @throws NotFoundException */ protected void initExtensions(IConfigurationElement configElem) throws CoreException, NotFoundException { /* * Init the resource factory extensions */ Collection<String> resourceFactories = getInitParams().getValues(InitParams.RESOURCEFACTORIES_INITPARAM); if (resourceFactories.size() == 0) { resourceFactories.add(DEFAULT_RESOURCEFACTORIES); } IExtensionRegistry registry = Platform.getExtensionRegistry(); for (String resourceFactory : resourceFactories) { IExtension extension = registry.getExtension(IResourceFactoryExtensionPoint.NAMESPACE, IResourceFactoryExtensionPoint.NAME, resourceFactory); if (extension == null) { throw new NotFoundException(resourceFactory); } for (IConfigurationElement member : extension.getConfigurationElements()) { IResourceFactory factory = (IResourceFactory) member.createExecutableExtension("class"); //$NON-NLS-1$ Properties props = new Properties(); for (String name : member.getAttributeNames()) { props.put(name, member.getAttribute(name)); } registerResourceFactory(new AggregatorExtension(extension, factory, props), null); } } /* * Init the module builder extensions */ Collection<String> moduleBuilders = getInitParams().getValues(InitParams.MODULEBUILDERS_INITPARAM); if (moduleBuilders.size() == 0) { moduleBuilders.add(DEFAULT_MODULEBUILDERS); } for (String moduleBuilder : moduleBuilders) { IExtension extension = registry.getExtension(IModuleBuilderExtensionPoint.NAMESPACE, IModuleBuilderExtensionPoint.NAME, moduleBuilder); if (extension == null) { throw new NotFoundException(moduleBuilder); } for (IConfigurationElement member : extension.getConfigurationElements()) { IModuleBuilder builder = (IModuleBuilder) member.createExecutableExtension("class"); //$NON-NLS-1$ Properties props = new Properties(); for (String name : member.getAttributeNames()) { props.put(name, member.getAttribute(name)); } registerModuleBuilder(new AggregatorExtension(extension, builder, props), null); } } /* * Init the http transport extension */ Collection<String> transports = getInitParams().getValues(InitParams.TRANSPORT_INITPARAM); if (transports.size() == 0) { transports.add(DEFAULT_HTTPTRANSPORT); } if (transports.size() != 1) { throw new IllegalStateException(transports.toString()); } String transportName = transports.iterator().next(); IExtension extension = registry.getExtension(IHttpTransportExtensionPoint.NAMESPACE, IHttpTransportExtensionPoint.NAME, transportName); if (extension == null) { throw new NotFoundException(transportName); } IConfigurationElement member = extension.getConfigurationElements()[0]; Properties props = new Properties(); IHttpTransport transport = (IHttpTransport) member.createExecutableExtension("class"); //$NON-NLS-1$ for (String attrname : member.getAttributeNames()) { props.put(attrname, member.getAttribute(attrname)); } registerHttpTransport(new AggregatorExtension(extension, transport, props)); /* * Now call setAggregator on the loaded extensions starting with the * transport and then the rest of the extensions. */ ExtensionRegistrar reg = new ExtensionRegistrar(); callExtensionInitializers(Arrays.asList(new IAggregatorExtension[] { getHttpTransportExtension() }), reg); callExtensionInitializers(getResourceFactoryExtensions(), reg); callExtensionInitializers(getModuleBuilderExtensions(), reg); reg.open = false; } // Instances of this class are NOT serializable private void writeObject(ObjectOutputStream out) throws IOException { throw new NotSerializableException(); } // Instances of this class are NOT serializable private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new NotSerializableException(); } /** * Returns the working directory for this aggregator. * <p> * This method is called during aggregator intialization. Subclasses may * override this method to initialize the aggregator using a different * working directory. Use the public {@link #getWorkingDirectory()} method * to get the working directory from an initialized aggregator. * * @param configElem * The configuration element. Not used by this class but provided * for use by subclasses. * @return The {@code File} object for the working directory * @throws FileNotFoundException */ protected File initWorkingDirectory(File defaultLocation, IConfigurationElement configElem) throws FileNotFoundException { String dirName = getOptions().getCacheDirectory(); File dirFile = null; if (dirName == null) { dirFile = defaultLocation; } else { // Make sure the path exists dirFile = new File(dirName); dirFile.mkdirs(); } if (!dirFile.exists()) { throw new FileNotFoundException(dirFile.toString()); } // Create a directory using the alias name within the contributing bundle's working // directory File workDir = new File(dirFile, getName()); // Create a bundle-version specific subdirectory. If the directory doesn't exist, assume // the bundle has been updated and clean out the workDir to remove all stale cache files. File servletDir = new File(workDir, Long.toString(getBundleContext().getBundle().getBundleId())); if (!servletDir.exists()) { FileUtils.deleteQuietly(workDir); } servletDir.mkdirs(); if (!servletDir.exists()) { throw new FileNotFoundException(servletDir.getAbsolutePath()); } return servletDir; } /** * Returns the name for this aggregator * <p> * This method is called during aggregator intialization. Subclasses may * override this method to initialize the aggregator using a different * name. Use the public {@link AggregatorImpl#getName()} method * to get the name of an initialized aggregator. * * @param configElem * The configuration element. * @return The aggregator name */ protected String getAggregatorName(IConfigurationElement configElem) { // trim leading and trailing '/' String alias = configElem.getAttribute("alias"); //$NON-NLS-1$ while (alias.charAt(0) == '/') alias = alias.substring(1); while (alias.charAt(alias.length() - 1) == '/') alias = alias.substring(0, alias.length() - 1); return alias; } /** * Returns the init params for this aggregator * <p> * This method is called during aggregator intialization. Subclasses may * override this method to initialize the aggregator using different * init params. Use the public {@link AggregatorImpl#getInitParams()} method * to get the init params for an initialized aggregator. * * @param configElem * The configuration element. * @return The init params */ protected InitParams getInitParams(IConfigurationElement configElem) { List<InitParam> initParams = new LinkedList<InitParam>(); IConfigurationElement[] children = configElem.getChildren("init-param"); //$NON-NLS-1$ for (IConfigurationElement child : children) { String name = child.getAttribute("name"); //$NON-NLS-1$ String value = child.getAttribute("value"); //$NON-NLS-1$ initParams.add(new InitParam(name, value)); } return new InitParams(initParams); } /** * Returns an opened ServiceTracker for the Aggregator options. Aggregator options * are created by the bundle activator and are shared by all Aggregator instances * created from the same bundle. * * @param bundleContext The contributing bundle context * @return The opened service tracker * @throws InvalidSyntaxException */ protected ServiceTracker getOptionsServiceTracker(BundleContext bundleContext) throws InvalidSyntaxException { ServiceTracker tracker = new ServiceTracker(bundleContext, bundleContext.createFilter("(&(" + Constants.OBJECTCLASS + "=" + IOptions.class.getName() + //$NON-NLS-1$ //$NON-NLS-2$ ")(name=" + getServletBundleName() + "))"), //$NON-NLS-1$ //$NON-NLS-2$ null); tracker.open(); return tracker; } /** * Returns an opened ServiceTracker for the Aggregator exectors provider. * The executors provider is are created by the bundle activator and is * shared by all Aggregator instances created from the same bundle. * * @param bundleContext * The contributing bundle context * @return The opened service tracker * @throws InvalidSyntaxException */ protected ServiceTracker getExecutorsServiceTracker(BundleContext bundleContext) throws InvalidSyntaxException { ServiceTracker tracker = new ServiceTracker(bundleContext, bundleContext.createFilter("(&(" + Constants.OBJECTCLASS + "=" + IExecutors.class.getName() + //$NON-NLS-1$ //$NON-NLS-2$ ")(name=" + getServletBundleName() + "))"), //$NON-NLS-1$ //$NON-NLS-2$ null); tracker.open(); return tracker; } protected ServiceTracker getVariableResolverServiceTracker(BundleContext bundleContext) throws InvalidSyntaxException { ServiceTracker tracker = new ServiceTracker(bundleContext, IVariableResolver.class.getName(), null); tracker.open(); return tracker; } /** * Instantiates a new dependencies object * * @return The new dependencies */ protected IDependencies newDependencies(long stamp) { return new DependenciesImpl(this, stamp); } /** * Instantiates a new config object * * @return The new config * @throws IOException */ protected IConfig newConfig() throws IOException { return new ConfigImpl(this); } /** * Instantiates a new cache manager * * @return The new cache manager */ protected ICacheManager newCacheManager(long stamp) throws IOException { return new CacheManagerImpl(this, stamp); } /** * Implements the {@link IExtensionRegistrar} interface */ protected class ExtensionRegistrar implements IExtensionRegistrar { private boolean open = true; /* (non-Javadoc) * @see com.ibm.jaggr.service.IExtensionInitializer.IExtensionRegistrar#registerExtension(java.lang.Object, java.util.Properties, java.lang.String, java.lang.String) */ @Override public void registerExtension(Object impl, Properties attributes, String extensionPointId, String uniqueId, IAggregatorExtension before) { if (!open) { throw new IllegalStateException("ExtensionRegistrar is closed"); //$NON-NLS-1$ } IAggregatorExtension extension; if (impl instanceof IResourceFactory) { extension = new AggregatorExtension(impl, new Properties(attributes), extensionPointId, uniqueId); registerResourceFactory(extension, before); } else if (impl instanceof IModuleBuilder) { extension = new AggregatorExtension(impl, attributes, extensionPointId, uniqueId); registerModuleBuilder(extension, before); } else { throw new UnsupportedOperationException(impl.getClass().getName()); } if (impl instanceof IExtensionInitializer) { ((IExtensionInitializer) impl).initialize(AggregatorImpl.this, extension, this); } } } }