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