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