/* * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * bstefanescu * * $Id$ */ package org.nuxeo.ecm.webengine.model.impl; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.ws.rs.core.MediaType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.utils.Path; import org.nuxeo.ecm.webengine.ResourceBinding; import org.nuxeo.ecm.webengine.WebEngine; import org.nuxeo.ecm.webengine.WebException; import org.nuxeo.ecm.webengine.model.AdapterNotFoundException; import org.nuxeo.ecm.webengine.model.AdapterType; import org.nuxeo.ecm.webengine.model.LinkDescriptor; import org.nuxeo.ecm.webengine.model.Messages; import org.nuxeo.ecm.webengine.model.Module; import org.nuxeo.ecm.webengine.model.Resource; import org.nuxeo.ecm.webengine.model.ResourceType; import org.nuxeo.ecm.webengine.model.TypeNotFoundException; import org.nuxeo.ecm.webengine.model.WebContext; import org.nuxeo.ecm.webengine.scripting.ScriptFile; import com.sun.jersey.server.impl.inject.ServerInjectableProviderContext; /** * The default implementation for a web configuration. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class ModuleImpl implements Module { private static final Log log = LogFactory.getLog(ModuleImpl.class); protected final WebEngine engine; protected final Object typeLock = new Object(); protected TypeRegistry typeReg; protected final ModuleConfiguration configuration; protected final ServerInjectableProviderContext sic; protected final ModuleImpl superModule; protected LinkRegistry linkReg; protected final String skinPathPrefix; /** * @deprecated Use {@link WebApplication} to declare modules - modules may have multiple roots * @return */ @Deprecated protected ResourceType rootType; protected Messages messages; protected DirectoryStack dirStack; // cache used for resolved files protected ConcurrentMap<String, ScriptFile> fileCache; public ModuleImpl(WebEngine engine, ModuleImpl superModule, ModuleConfiguration config, ServerInjectableProviderContext sic) { this.engine = engine; this.superModule = superModule; this.sic = sic; configuration = config; skinPathPrefix = new StringBuilder().append(engine.getSkinPathPrefix()).append('/').append(config.name).toString(); fileCache = new ConcurrentHashMap<String, ScriptFile>(); loadConfiguration(); reloadMessages(); loadDirectoryStack(); } /** * Whether or not this module has a GUI and should be listed in available GUI module list. For example, REST modules * usually don't have a GUI. * * @return true if headless (no GUI is provided), false otherwise */ public boolean isHeadless() { return configuration.isHeadless; } /** * @return the natures, or null if no natures were specified */ public Set<String> getNatures() { return configuration.natures; } public boolean hasNature(String natureId) { return configuration.natures != null && configuration.natures.contains(natureId); } @Override public WebEngine getEngine() { return engine; } @Override public String getName() { return configuration.name; } @Override public ModuleImpl getSuperModule() { return superModule; } public ModuleConfiguration getModuleConfiguration() { return configuration; } /** * @deprecated Use {@link WebApplication} to declare modules * @return */ @Deprecated public ResourceType getRootType() { // force type registry creation if needed getTypeRegistry(); if (rootType == null) { throw new IllegalStateException("You use new web module declaration - should not call this compat. method"); } return rootType; } /** * @deprecated Use {@link WebApplication} to declare modules * @return */ @Override @Deprecated public Resource getRootObject(WebContext ctx) { ((AbstractWebContext) ctx).setModule(this); Resource obj = ctx.newObject(getRootType()); obj.setRoot(true); return obj; } @Override public String getSkinPathPrefix() { return skinPathPrefix; } public TypeRegistry getTypeRegistry() { if (typeReg == null) { // create type registry if not already created synchronized (typeLock) { if (typeReg == null) { typeReg = createTypeRegistry(); if (configuration.rootType != null) { // compatibility code for avoiding NPE rootType = typeReg.getType(configuration.rootType); } } } } return typeReg; } @Override public Class<?> loadClass(String className) throws ClassNotFoundException { return engine.loadClass(className); } @Override public ResourceType getType(String typeName) { ResourceType type = getTypeRegistry().getType(typeName); if (type == null) { throw new TypeNotFoundException(typeName); } return type; } @Override public ResourceType[] getTypes() { return getTypeRegistry().getTypes(); } @Override public AdapterType[] getAdapters() { return getTypeRegistry().getAdapters(); } @Override public AdapterType getAdapter(Resource ctx, String name) { AdapterType type = getTypeRegistry().getAdapter(ctx, name); if (type == null) { throw new AdapterNotFoundException(ctx, name); } return type; } @Override public List<String> getAdapterNames(Resource ctx) { return getTypeRegistry().getAdapterNames(ctx); } @Override public List<AdapterType> getAdapters(Resource ctx) { return getTypeRegistry().getAdapters(ctx); } @Override public List<String> getEnabledAdapterNames(Resource ctx) { return getTypeRegistry().getEnabledAdapterNames(ctx); } @Override public List<AdapterType> getEnabledAdapters(Resource ctx) { return getTypeRegistry().getEnabledAdapters(ctx); } @Override public String getMediaTypeId(MediaType mt) { if (configuration.mediatTypeRefs == null) { return null; } MediaTypeRef[] refs = configuration.mediatTypeRefs; for (MediaTypeRef ref : refs) { String id = ref.match(mt); if (id != null) { return id; } } return null; } @Override public List<ResourceBinding> getResourceBindings() { return configuration.resources; } @Override public boolean isDerivedFrom(String moduleName) { if (configuration.name.equals(moduleName)) { return true; } if (superModule != null) { return superModule.isDerivedFrom(moduleName); } return false; } public void loadConfiguration() { linkReg = new LinkRegistry(); if (configuration.links != null) { for (LinkDescriptor link : configuration.links) { linkReg.registerLink(link); } } configuration.links = null; // avoid storing unused data } @Override public List<LinkDescriptor> getLinks(String category) { return linkReg.getLinks(category); } @Override public List<LinkDescriptor> getActiveLinks(Resource context, String category) { return linkReg.getActiveLinks(context, category); } public LinkRegistry getLinkRegistry() { return linkReg; } @Override public String getTemplateFileExt() { return configuration.templateFileExt; } public void flushSkinCache() { log.info("Flushing skin cache for module: " + getName()); fileCache = new ConcurrentHashMap<String, ScriptFile>(); } public void flushTypeCache() { log.info("Flushing type cache for module: " + getName()); synchronized (typeLock) { // remove type cache files if any new DefaultTypeLoader(this, typeReg, configuration.directory).flushCache(); typeReg = null; // type registry will be recreated on first access } } /** * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. */ @Deprecated public void flushRootResourcesCache() { if (configuration.resources != null) { // reregister resources for (ResourceBinding rb : configuration.resources) { try { engine.removeResourceBinding(rb); rb.reload(engine); engine.addResourceBinding(rb); } catch (ClassNotFoundException e) { log.error("Failed to reload resource", e); } } } } @Override public void flushCache() { reloadMessages(); flushSkinCache(); flushTypeCache(); } public static File getSkinDir(File moduleDir) { return new File(moduleDir, "skin"); } protected void loadDirectoryStack() { dirStack = new DirectoryStack(); try { File skin = getSkinDir(configuration.directory); if (!configuration.allowHostOverride) { if (skin.isDirectory()) { dirStack.addDirectory(skin); } } for (File fragmentDir : configuration.fragmentDirectories) { File fragmentSkin = getSkinDir(fragmentDir); if (fragmentSkin.isDirectory()) { dirStack.addDirectory(fragmentSkin); } } if (configuration.allowHostOverride) { if (skin.isDirectory()) { dirStack.addDirectory(skin); } } if (superModule != null) { DirectoryStack ds = superModule.dirStack; if (ds != null) { dirStack.getDirectories().addAll(ds.getDirectories()); } } } catch (IOException e) { throw WebException.wrap("Failed to load directories stack", e); } } @Override public ScriptFile getFile(String path) { int len = path.length(); if (len == 0) { return null; } char c = path.charAt(0); if (c == '.') { // avoid getting files outside the web root path = new Path(path).makeAbsolute().toString(); } else if (c != '/') {// avoid doing duplicate entries in document stack // cache path = new StringBuilder(len + 1).append("/").append(path).toString(); } try { return findFile(new Path(path).makeAbsolute().toString()); } catch (IOException e) { throw WebException.wrap(e); } } /** * @param path a normalized path (absolute path) */ protected ScriptFile findFile(String path) throws IOException { ScriptFile file = fileCache.get(path); if (file == null) { File f = dirStack.getFile(path); if (f != null) { file = new ScriptFile(f); fileCache.put(path, file); } } return file; } @Override public ScriptFile getSkinResource(String path) throws IOException { File file = dirStack.getFile(path); if (file != null) { return new ScriptFile(file); } return null; } /** * TODO There are no more reasons to lazy load the type registry since module are lazy loaded. Type registry must be * loaded at module creation */ public TypeRegistry createTypeRegistry() { // double s = System.currentTimeMillis(); TypeRegistry typeReg = null; // install types from super modules if (superModule != null) { // TODO add type reg listener on super // modules to update types when needed? typeReg = new TypeRegistry(superModule.getTypeRegistry(), engine, this); } else { typeReg = new TypeRegistry(engine, this); } if (configuration.directory.isDirectory()) { DefaultTypeLoader loader = new DefaultTypeLoader(this, typeReg, configuration.directory); loader.load(); } // System.out.println(">>>>>>>>>>>>>"+((System.currentTimeMillis()-s)/1000)); return typeReg; } @Override public File getRoot() { return configuration.directory; } public void reloadMessages() { messages = new Messages(superModule != null ? superModule.getMessages() : null, this); } @Override public Messages getMessages() { return messages; } @Override @SuppressWarnings("unchecked") public Map<String, String> getMessages(String language) { log.info("Loading i18n files for module " + configuration.name); File file = new File(configuration.directory, new StringBuilder().append("/i18n/messages_").append(language).append(".properties").toString()); InputStream in = null; try { in = new FileInputStream(file); Properties p = new Properties(); p.load(in); return new HashMap(p); // HashMap is faster than Properties } catch (IOException e) { return null; } finally { if (in != null) { try { in.close(); } catch (IOException ee) { log.error(ee); } } } } @Override public String toString() { return getName(); } }