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