001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.webengine.model.impl; 021 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030import java.util.Set; 031import java.util.concurrent.ConcurrentHashMap; 032import java.util.concurrent.ConcurrentMap; 033 034import javax.ws.rs.core.MediaType; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.common.utils.Path; 039import org.nuxeo.ecm.webengine.ResourceBinding; 040import org.nuxeo.ecm.webengine.WebEngine; 041import org.nuxeo.ecm.webengine.WebException; 042import org.nuxeo.ecm.webengine.model.AdapterNotFoundException; 043import org.nuxeo.ecm.webengine.model.AdapterType; 044import org.nuxeo.ecm.webengine.model.LinkDescriptor; 045import org.nuxeo.ecm.webengine.model.Messages; 046import org.nuxeo.ecm.webengine.model.Module; 047import org.nuxeo.ecm.webengine.model.Resource; 048import org.nuxeo.ecm.webengine.model.ResourceType; 049import org.nuxeo.ecm.webengine.model.TypeNotFoundException; 050import org.nuxeo.ecm.webengine.model.WebContext; 051import org.nuxeo.ecm.webengine.scripting.ScriptFile; 052 053/** 054 * The default implementation for a web configuration. 055 * 056 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 057 */ 058public class ModuleImpl implements Module { 059 060 private static final Log log = LogFactory.getLog(ModuleImpl.class); 061 062 protected final WebEngine engine; 063 064 protected final Object typeLock = new Object(); 065 066 protected TypeRegistry typeReg; 067 068 protected final ModuleConfiguration configuration; 069 070 protected final ModuleImpl superModule; 071 072 protected LinkRegistry linkReg; 073 074 protected final String skinPathPrefix; 075 076 /** 077 * @deprecated Use {@link WebApplication} to declare modules - modules may have multiple roots 078 * @return 079 */ 080 @Deprecated 081 protected ResourceType rootType; 082 083 protected Messages messages; 084 085 protected DirectoryStack dirStack; 086 087 // cache used for resolved files 088 protected ConcurrentMap<String, ScriptFile> fileCache; 089 090 public ModuleImpl(WebEngine engine, ModuleImpl superModule, ModuleConfiguration config) { 091 this.engine = engine; 092 this.superModule = superModule; 093 configuration = config; 094 skinPathPrefix = new StringBuilder().append(engine.getSkinPathPrefix()).append('/').append(config.name).toString(); 095 fileCache = new ConcurrentHashMap<String, ScriptFile>(); 096 loadConfiguration(); 097 reloadMessages(); 098 loadDirectoryStack(); 099 } 100 101 /** 102 * Whether or not this module has a GUI and should be listed in available GUI module list. For example, REST modules 103 * usually don't have a GUI. 104 * 105 * @return true if headless (no GUI is provided), false otherwise 106 */ 107 public boolean isHeadless() { 108 return configuration.isHeadless; 109 } 110 111 /** 112 * @return the natures, or null if no natures were specified 113 */ 114 public Set<String> getNatures() { 115 return configuration.natures; 116 } 117 118 public boolean hasNature(String natureId) { 119 return configuration.natures != null && configuration.natures.contains(natureId); 120 } 121 122 @Override 123 public WebEngine getEngine() { 124 return engine; 125 } 126 127 @Override 128 public String getName() { 129 return configuration.name; 130 } 131 132 @Override 133 public ModuleImpl getSuperModule() { 134 return superModule; 135 } 136 137 public ModuleConfiguration getModuleConfiguration() { 138 return configuration; 139 } 140 141 /** 142 * @deprecated Use {@link WebApplication} to declare modules 143 * @return 144 */ 145 @Deprecated 146 public ResourceType getRootType() { 147 // force type registry creation if needed 148 getTypeRegistry(); 149 if (rootType == null) { 150 throw new IllegalStateException("You use new web module declaration - should not call this compat. method"); 151 } 152 return rootType; 153 } 154 155 /** 156 * @deprecated Use {@link WebApplication} to declare modules 157 * @return 158 */ 159 @Override 160 @Deprecated 161 public Resource getRootObject(WebContext ctx) { 162 ((AbstractWebContext) ctx).setModule(this); 163 Resource obj = ctx.newObject(getRootType()); 164 obj.setRoot(true); 165 return obj; 166 } 167 168 @Override 169 public String getSkinPathPrefix() { 170 return skinPathPrefix; 171 } 172 173 public TypeRegistry getTypeRegistry() { 174 if (typeReg == null) { // create type registry if not already created 175 synchronized (typeLock) { 176 if (typeReg == null) { 177 typeReg = createTypeRegistry(); 178 if (configuration.rootType != null) { 179 // compatibility code for avoiding NPE 180 rootType = typeReg.getType(configuration.rootType); 181 } 182 } 183 } 184 } 185 return typeReg; 186 } 187 188 @Override 189 public Class<?> loadClass(String className) throws ClassNotFoundException { 190 return engine.loadClass(className); 191 } 192 193 @Override 194 public ResourceType getType(String typeName) { 195 ResourceType type = getTypeRegistry().getType(typeName); 196 if (type == null) { 197 throw new TypeNotFoundException(typeName); 198 } 199 return type; 200 } 201 202 @Override 203 public ResourceType[] getTypes() { 204 return getTypeRegistry().getTypes(); 205 } 206 207 @Override 208 public AdapterType[] getAdapters() { 209 return getTypeRegistry().getAdapters(); 210 } 211 212 @Override 213 public AdapterType getAdapter(Resource ctx, String name) { 214 AdapterType type = getTypeRegistry().getAdapter(ctx, name); 215 if (type == null) { 216 throw new AdapterNotFoundException(ctx, name); 217 } 218 return type; 219 } 220 221 @Override 222 public List<String> getAdapterNames(Resource ctx) { 223 return getTypeRegistry().getAdapterNames(ctx); 224 } 225 226 @Override 227 public List<AdapterType> getAdapters(Resource ctx) { 228 return getTypeRegistry().getAdapters(ctx); 229 } 230 231 @Override 232 public List<String> getEnabledAdapterNames(Resource ctx) { 233 return getTypeRegistry().getEnabledAdapterNames(ctx); 234 } 235 236 @Override 237 public List<AdapterType> getEnabledAdapters(Resource ctx) { 238 return getTypeRegistry().getEnabledAdapters(ctx); 239 } 240 241 @Override 242 public String getMediaTypeId(MediaType mt) { 243 if (configuration.mediatTypeRefs == null) { 244 return null; 245 } 246 MediaTypeRef[] refs = configuration.mediatTypeRefs; 247 for (MediaTypeRef ref : refs) { 248 String id = ref.match(mt); 249 if (id != null) { 250 return id; 251 } 252 } 253 return null; 254 } 255 256 @Override 257 public List<ResourceBinding> getResourceBindings() { 258 return configuration.resources; 259 } 260 261 @Override 262 public boolean isDerivedFrom(String moduleName) { 263 if (configuration.name.equals(moduleName)) { 264 return true; 265 } 266 if (superModule != null) { 267 return superModule.isDerivedFrom(moduleName); 268 } 269 return false; 270 } 271 272 public void loadConfiguration() { 273 linkReg = new LinkRegistry(); 274 if (configuration.links != null) { 275 for (LinkDescriptor link : configuration.links) { 276 linkReg.registerLink(link); 277 } 278 } 279 configuration.links = null; // avoid storing unused data 280 } 281 282 @Override 283 public List<LinkDescriptor> getLinks(String category) { 284 return linkReg.getLinks(category); 285 } 286 287 @Override 288 public List<LinkDescriptor> getActiveLinks(Resource context, String category) { 289 return linkReg.getActiveLinks(context, category); 290 } 291 292 public LinkRegistry getLinkRegistry() { 293 return linkReg; 294 } 295 296 @Override 297 public String getTemplateFileExt() { 298 return configuration.templateFileExt; 299 } 300 301 public void flushSkinCache() { 302 log.info("Flushing skin cache for module: " + getName()); 303 fileCache = new ConcurrentHashMap<String, ScriptFile>(); 304 } 305 306 public void flushTypeCache() { 307 log.info("Flushing type cache for module: " + getName()); 308 synchronized (typeLock) { 309 // remove type cache files if any 310 new DefaultTypeLoader(this, typeReg, configuration.directory).flushCache(); 311 typeReg = null; // type registry will be recreated on first access 312 } 313 } 314 315 /** 316 * @deprecated resources are deprecated - you should use a jax-rs application to declare more resources. 317 */ 318 @Deprecated 319 public void flushRootResourcesCache() { 320 if (configuration.resources != null) { // reregister resources 321 for (ResourceBinding rb : configuration.resources) { 322 try { 323 engine.removeResourceBinding(rb); 324 rb.reload(engine); 325 engine.addResourceBinding(rb); 326 } catch (ClassNotFoundException e) { 327 log.error("Failed to reload resource", e); 328 } 329 } 330 } 331 } 332 333 @Override 334 public void flushCache() { 335 reloadMessages(); 336 flushSkinCache(); 337 flushTypeCache(); 338 } 339 340 public static File getSkinDir(File moduleDir) { 341 return new File(moduleDir, "skin"); 342 } 343 344 protected void loadDirectoryStack() { 345 dirStack = new DirectoryStack(); 346 try { 347 File skin = getSkinDir(configuration.directory); 348 if (!configuration.allowHostOverride) { 349 if (skin.isDirectory()) { 350 dirStack.addDirectory(skin); 351 } 352 } 353 for (File fragmentDir : configuration.fragmentDirectories) { 354 File fragmentSkin = getSkinDir(fragmentDir); 355 if (fragmentSkin.isDirectory()) { 356 dirStack.addDirectory(fragmentSkin); 357 } 358 } 359 if (configuration.allowHostOverride) { 360 if (skin.isDirectory()) { 361 dirStack.addDirectory(skin); 362 } 363 } 364 if (superModule != null) { 365 DirectoryStack ds = superModule.dirStack; 366 if (ds != null) { 367 dirStack.getDirectories().addAll(ds.getDirectories()); 368 } 369 } 370 } catch (IOException e) { 371 throw WebException.wrap("Failed to load directories stack", e); 372 } 373 } 374 375 @Override 376 public ScriptFile getFile(String path) { 377 int len = path.length(); 378 if (len == 0) { 379 return null; 380 } 381 char c = path.charAt(0); 382 if (c == '.') { // avoid getting files outside the web root 383 path = new Path(path).makeAbsolute().toString(); 384 } else if (c != '/') {// avoid doing duplicate entries in document stack 385 // cache 386 path = new StringBuilder(len + 1).append("/").append(path).toString(); 387 } 388 try { 389 return findFile(new Path(path).makeAbsolute().toString()); 390 } catch (IOException e) { 391 throw WebException.wrap(e); 392 } 393 } 394 395 /** 396 * @param path a normalized path (absolute path) 397 */ 398 protected ScriptFile findFile(String path) throws IOException { 399 ScriptFile file = fileCache.get(path); 400 if (file == null) { 401 File f = dirStack.getFile(path); 402 if (f != null) { 403 file = new ScriptFile(f); 404 fileCache.put(path, file); 405 } 406 } 407 return file; 408 } 409 410 @Override 411 public ScriptFile getSkinResource(String path) throws IOException { 412 File file = dirStack.getFile(path); 413 if (file != null) { 414 return new ScriptFile(file); 415 } 416 return null; 417 } 418 419 /** 420 * TODO There are no more reasons to lazy load the type registry since module are lazy loaded. Type registry must be 421 * loaded at module creation 422 */ 423 public TypeRegistry createTypeRegistry() { 424 // double s = System.currentTimeMillis(); 425 TypeRegistry typeReg = null; 426 // install types from super modules 427 if (superModule != null) { // TODO add type reg listener on super 428 // modules to update types when needed? 429 typeReg = new TypeRegistry(superModule.getTypeRegistry(), engine, this); 430 } else { 431 typeReg = new TypeRegistry(new TypeRegistry(engine, null), engine, this); 432 } 433 if (configuration.directory.isDirectory()) { 434 DefaultTypeLoader loader = new DefaultTypeLoader(this, typeReg, configuration.directory); 435 loader.load(); 436 } 437 // System.out.println(">>>>>>>>>>>>>"+((System.currentTimeMillis()-s)/1000)); 438 return typeReg; 439 } 440 441 @Override 442 public File getRoot() { 443 return configuration.directory; 444 } 445 446 public void reloadMessages() { 447 messages = new Messages(superModule != null ? superModule.getMessages() : null, this); 448 } 449 450 @Override 451 public Messages getMessages() { 452 return messages; 453 } 454 455 @Override 456 @SuppressWarnings("unchecked") 457 public Map<String, String> getMessages(String language) { 458 log.info("Loading i18n files for module " + configuration.name); 459 File file = new File(configuration.directory, 460 new StringBuilder().append("/i18n/messages_").append(language).append(".properties").toString()); 461 InputStream in = null; 462 try { 463 in = new FileInputStream(file); 464 Properties p = new Properties(); 465 p.load(in); 466 return new HashMap(p); // HashMap is faster than Properties 467 } catch (IOException e) { 468 return null; 469 } finally { 470 if (in != null) { 471 try { 472 in.close(); 473 } catch (IOException ee) { 474 log.error(ee); 475 } 476 } 477 } 478 } 479 480 @Override 481 public String toString() { 482 return getName(); 483 } 484 485}