/** * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.commons.chromattic; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.chromattic.api.Chromattic; import org.chromattic.api.ChromatticBuilder; import org.chromattic.api.UndeclaredRepositoryException; import org.exoplatform.container.component.BaseComponentPlugin; import org.exoplatform.container.xml.InitParams; import org.exoplatform.container.xml.PropertiesParam; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; /** * <p> * The chromattic life cycle objets is a plugin that allow to bootstrap a chromattic builder and make it managed either locally * or globally. * </p> * * <p> * It is allowed to create subclasses of this class to override the methods {@link #onOpenSession(SessionContext)} or * {@link #onCloseSession(SessionContext)} to perform additional treatment on the session context at a precise phase of its life * cycle. * </p> * * <p> * The life cycle domain uniquely identifies the chromattic domain among all domain registered against the * {@link org.exoplatform.commons.chromattic.ChromatticManager} manager. * </p> * * <p> * The plugin takes an instance of {@link org.exoplatform.container.xml.InitParams} as parameter that contains the following * entries: * * <ul> * <li>The <code>domain-name</code> string that is the life cycle domain name</li> * <li>The <code>workspace-name</code> string that is the repository workspace name associated with this life cycle</li> * <li>The <code>entities</code> list value that contains the list of chromattic entities that will be registered against the * builder chromattic builder</li> * </ul> * </p> * * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> * @version $Revision$ */ public class ChromatticLifeCycle extends BaseComponentPlugin { /** . */ private static final Map<String, ChromatticBuilder.Option<?>> RECOGNIZED_OPTIONS; static { Map<String, ChromatticBuilder.Option<?>> options = new HashMap<String, ChromatticBuilder.Option<?>>(); for (ChromatticBuilder.Option<?> option : new ChromatticBuilder.Option<?>[] { ChromatticBuilder.JCR_OPTIMIZE_ENABLED, ChromatticBuilder.JCR_OPTIMIZE_HAS_NODE_ENABLED, ChromatticBuilder.JCR_OPTIMIZE_HAS_PROPERTY_ENABLED, ChromatticBuilder.OBJECT_FORMATTER_CLASSNAME, ChromatticBuilder.ROOT_NODE_PATH, ChromatticBuilder.CREATE_ROOT_NODE }) { options.put(option.getName(), option); } RECOGNIZED_OPTIONS = options; } /** . */ private final String domainName; /** . */ private final String workspaceName; /** . */ Chromattic realChromattic; /** . */ private ChromatticImpl chromattic; /** . */ ChromatticManager manager; /** . */ private final List<String> entityClassNames; /** . */ final ThreadLocal<LocalContext> currentContext = new ThreadLocal<LocalContext>(); /** . */ final Logger log = LoggerFactory.getLogger(ChromatticLifeCycle.class); /** . */ private final Map<String, String> optionMap; public ChromatticLifeCycle(InitParams params) { Map<String, String> options = new HashMap<String, String>(); PropertiesParam pp = params.getPropertiesParam("options"); if (pp != null) { options.putAll(pp.getProperties()); } this.domainName = params.getValueParam("domain-name").getValue(); this.workspaceName = params.getValueParam("workspace-name").getValue(); this.entityClassNames = params.getValuesParam("entities").getValues(); this.optionMap = options; } public String getDomainName() { return domainName; } public final String getRepositoryName() { try { return manager.repositoryService.getCurrentRepository().getConfiguration().getName(); } catch (RepositoryException e) { throw new UndeclaredRepositoryException("JCR exceptions are really bad", e); } } public final String getWorkspaceName() { return workspaceName; } public final Chromattic getChromattic() { return chromattic; } public final ChromatticManager getManager() { return manager; } AbstractContext getLoginContext() { Synchronization sync = manager.getSynchronization(); // if (sync != null) { return sync.getContext(domainName); } // return currentContext.get(); } /** * Returns <code>#getContext(false)</code>. * * @see #getContext(boolean) * @return a session context */ public final SessionContext getContext() { return getContext(false); } /** * A best effort to return a session context whether it's local or global. * * @param peek true if no context should be automatically created * @return a session context */ public final SessionContext getContext(boolean peek) { log.trace("Requesting context"); Synchronization sync = manager.getSynchronization(); // if (sync != null) { log.trace("Found synchronization about to get the current context for chromattic " + domainName); SynchronizedContext context = sync.getContext(domainName); // if (context == null && !peek) { log.trace("No current context found, about to open one"); context = sync.openContext(this); } else { log.trace("Found a context and will return it"); } // return context; } // log.trace("No active synchronization about to try the current local context"); LocalContext localContext = currentContext.get(); log.trace("Found local context " + localContext); // return localContext; } final SessionContext openSynchronizedContext() { log.trace("Opening a global context"); AbstractContext context = (AbstractContext) getContext(true); // if (context != null) { String msg = "A global context is already opened"; log.trace(msg); throw new IllegalStateException(msg); } // Attempt to get the synchronization log.trace("Ok, no global context found, asking current synchronization"); Synchronization sync = manager.getSynchronization(); // if (sync == null) { String msg = "Need global synchronization for opening a global context"; log.trace(msg); throw new IllegalStateException(msg); } // log.trace("Opening a global context for the related sync"); return sync.openContext(this); } /** * Opens a context and returns it. If there is a global ongoing synchronization then the context will be scoped to that * synchronization, otherwise it will be a local context. * * @return the session context */ public final SessionContext openContext() { log.trace("Opening a context"); AbstractContext context = (AbstractContext) getContext(true); // if (context != null) { String msg = "A context is already opened"; log.trace(msg); throw new IllegalStateException(msg); } // log.trace("Ok, no context found, asking current synchronization"); Synchronization sync = manager.getSynchronization(); // if (sync != null) { log.trace("Found a synchronization, about to open a global context"); context = sync.openContext(this); } else { log.trace("Not synchronization found, about to a local context"); LocalContext localContext = new LocalContext(this); currentContext.set(localContext); onOpenSession(localContext); context = localContext; } // return context; } public final void closeContext(boolean save) { log.trace("Requesting for context close with save=" + save + " going to look for any context"); AbstractContext context = (AbstractContext) getContext(true); // if (context == null) { String msg = "Cannot close non existing context"; log.trace(msg); throw new IllegalStateException(msg); } // context.close(save); } protected void onOpenSession(SessionContext context) { } protected void onCloseSession(SessionContext context) { } Session doLogin() throws RepositoryException { AbstractContext loginContext = getLoginContext(); // if (loginContext == null) { throw new IllegalStateException("Could not obtain a login context"); } // return loginContext.doLogin(); } public final void start() throws Exception { log.debug("About to setup Chromattic life cycle " + domainName); ChromatticBuilder builder = ChromatticBuilder.create(); // ClassLoader cl = Thread.currentThread().getContextClassLoader(); for (String className : entityClassNames) { String fqn = className.trim(); log.debug("Adding class " + fqn + " to life cycle " + domainName); Class<?> entityClass = cl.loadClass(fqn); builder.add(entityClass); } // Set up boot context PortalSessionLifeCycle.bootContext.set(this); // try { for (Map.Entry<String, String> optionEntry : optionMap.entrySet()) { ChromatticBuilder.Option<?> option = RECOGNIZED_OPTIONS.get(optionEntry.getKey()); if (option != null) { log.debug("Setting Chromattic option " + optionEntry); setOption(builder, option, optionEntry.getValue()); } } // Set it now, so we are sure that it will be the correct life cycle builder.setOptionValue(ChromatticBuilder.SESSION_LIFECYCLE_CLASSNAME, PortalSessionLifeCycle.class.getName()); // log.debug("Building Chromattic " + domainName); realChromattic = builder.build(); chromattic = new ChromatticImpl(this); } catch (Exception e) { log.error("Could not start Chromattic " + domainName, e); } finally { PortalSessionLifeCycle.bootContext.set(null); } } private <D> void setOption(ChromatticBuilder builder, ChromatticBuilder.Option<D> option, String value) { log.debug("Setting Chromattic option " + option.getDisplayName()); builder.setOptionValue(option, option.getInstance(value).getValue()); } public final void stop() { // Nothing to do for now } }