/**
* 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.portal.pom.config;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.chromattic.api.ChromatticSession;
import org.chromattic.api.UndeclaredRepositoryException;
import org.chromattic.api.query.QueryBuilder;
import org.chromattic.api.query.QueryResult;
import org.chromattic.ext.format.BaseEncodingObjectFormatter;
import org.exoplatform.commons.chromattic.SessionContext;
import org.exoplatform.commons.chromattic.SynchronizationListener;
import org.exoplatform.commons.chromattic.SynchronizationStatus;
import org.exoplatform.portal.config.NoSuchDataException;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.mop.api.Model;
import org.gatein.mop.api.content.Customization;
import org.gatein.mop.api.workspace.ObjectType;
import org.gatein.mop.api.workspace.Site;
import org.gatein.mop.api.workspace.Workspace;
import org.gatein.mop.api.workspace.WorkspaceObject;
import org.gatein.mop.core.api.ModelImpl;
import org.gatein.mop.core.api.workspace.NavigationImpl;
import org.gatein.mop.core.api.workspace.PageImpl;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public final class POMSession {
/** . */
private static final Logger log = LoggerFactory.getLogger(POMSession.class);
/** . */
private static final Map<ObjectType<?>, Class> mapping = new HashMap<ObjectType<?>, Class>();
static {
mapping.put(ObjectType.PAGE, PageImpl.class);
mapping.put(ObjectType.NAVIGATION, NavigationImpl.class);
}
/** . */
final POMSessionManager mgr;
/** . */
private ModelImpl model;
/** . */
private boolean isInTask;
/** . */
private boolean markedForRollback;
/** . */
private List<Serializable> staleKeys;
/** . */
private boolean modified;
/** . */
private SessionContext context;
/** . */
private MOPChromatticLifeCycle configurator;
public POMSession(POMSessionManager mgr, MOPChromatticLifeCycle configurator, SessionContext context) {
// Register for cache eviction
context.addSynchronizationListener(listener);
//
this.mgr = mgr;
this.isInTask = false;
this.markedForRollback = false;
this.staleKeys = null;
this.configurator = configurator;
this.context = context;
}
public Object getFromCache(Serializable key) {
if (isModified()) {
throw new IllegalStateException("Cannot read object in shared cache from a modified session");
}
return mgr.cacheGet(key);
}
public void putInCache(Serializable key, Object value) {
if (isModified()) {
throw new IllegalStateException("Cannot put object in shared cache from a modified session");
}
mgr.cachePut(key, value);
}
public void scheduleForEviction(Serializable key) {
if (key == null) {
throw new NullPointerException();
}
if (staleKeys == null) {
staleKeys = new LinkedList<Serializable>();
}
staleKeys.add(key);
}
private Model getModel() {
if (model == null) {
model = mgr.getPOMService().getModel();
}
return model;
}
public boolean isModified() {
if (modified) {
return true;
}
try {
ChromatticSession session = getSession();
Session jcrSession = session.getJCRSession();
modified = jcrSession.hasPendingChanges();
} catch (RepositoryException e) {
throw new UndeclaredRepositoryException(e);
}
return modified;
}
protected ChromatticSession getSession() {
return context.getSession();
}
public Workspace getWorkspace() {
return getModel().getWorkspace();
}
public boolean isMarkedForRollback() {
return markedForRollback;
}
public String pathOf(WorkspaceObject o) {
return getModel().pathOf(o);
}
public <O extends WorkspaceObject> Iterator<O> findObject(ObjectType<O> ownerType, String statement) {
this.save();
return getModel().findObject(ownerType, statement);
}
public <O extends WorkspaceObject> O findObjectById(ObjectType<O> ownerType, String id) {
return getModel().findObjectById(ownerType, id);
}
public WorkspaceObject findObjectById(String id) {
return findObjectById(ObjectType.ANY, id);
}
public Customization<?> findCustomizationById(String id) {
Customization<?> customization = getModel().findCustomizationById(id);
if (customization == null) {
throw new NoSuchDataException("Can not find " + id);
}
return customization;
}
public POMSessionManager getManager() {
return mgr;
}
private static final BaseEncodingObjectFormatter formatter = new BaseEncodingObjectFormatter();
public <O extends WorkspaceObject> QueryResult<O> findObjects(ObjectType<O> type, ObjectType<Site> siteType,
String ownerId, String title, int offset, int limit) {
this.save();
//
String ownerIdChunk = "%";
if (ownerId != null) {
ownerId = ownerId.trim();
if (!ownerId.isEmpty()) {
ownerIdChunk = "mop:" + formatter.encodeNodeName(null, ownerId);
}
}
//
String ownerTypeChunk;
if (siteType != null) {
if (siteType == ObjectType.PORTAL_SITE) {
ownerTypeChunk = "mop:portalsites";
} else if (siteType == ObjectType.GROUP_SITE) {
ownerTypeChunk = "mop:groupsites";
} else {
ownerTypeChunk = "mop:usersites";
}
} else {
ownerTypeChunk = "%";
}
//
Workspace workspace = getWorkspace();
String workspaceChunk = model.pathOf(workspace);
//
String statement;
try {
if (title != null) {
title = Utils.queryEscape(title);
if (type == ObjectType.PAGE) {
statement = "jcr:path LIKE '" + workspaceChunk + "/" + ownerTypeChunk + "/" + ownerIdChunk
+ "/mop:rootpage/mop:children/mop:pages/mop:children/%' AND " + "(" + "LOWER(gtn:name) LIKE '%"
+ title.trim().toLowerCase() + "%' ESCAPE '\\')";
} else {
throw new UnsupportedOperationException();
}
} else {
if (type == ObjectType.PAGE) {
statement = "jcr:path LIKE '" + workspaceChunk + "/" + ownerTypeChunk + "/" + ownerIdChunk
+ "/mop:rootpage/mop:children/mop:pages/mop:children/%'";
} else {
statement = "jcr:path LIKE '" + workspaceChunk + "/" + ownerTypeChunk + "/" + ownerIdChunk
+ "/mop:rootnavigation/mop:children/mop:default'";
}
}
} catch (IllegalArgumentException e) {
if (type == ObjectType.PAGE) {
statement = "jcr:path LIKE ''";
} else {
statement = "jcr:path LIKE ''";
}
}
String defaultOrderBy = "gtn:name";
// Temporary work around, to fix in MOP and then remove
ChromatticSession session = context.getSession();
Class<O> mappedClass = (Class<O>) mapping.get(type);
QueryBuilder<O> queryBuilder;
if (type == ObjectType.PAGE) {
queryBuilder = session.createQueryBuilder(mappedClass).where(statement).orderBy(defaultOrderBy);
} else {
queryBuilder = session.createQueryBuilder(mappedClass).where(statement);
}
return queryBuilder.get().objects((long) offset, (long) limit);
}
private final SynchronizationListener listener = new SynchronizationListener() {
public void beforeSynchronization() {
}
public void afterSynchronization(SynchronizationStatus status) {
if (status == SynchronizationStatus.SAVED) {
reset();
}
}
};
/**
* Reset the session and set its state like it was a newly created session.
*/
private void reset() {
// Evict entries from the shared cache if any
if (staleKeys != null && staleKeys.size() > 0) {
if (log.isTraceEnabled()) {
log.trace("About to evict entries " + staleKeys);
}
for (Serializable key : staleKeys) {
mgr.cacheRemove(key);
}
staleKeys.clear();
}
// Reset modified flag
if (log.isTraceEnabled()) {
log.trace("Setting modified flag to false");
}
modified = false;
}
public <V> V execute(POMTask<V> task) throws Exception {
if (isInTask) {
throw new IllegalStateException();
}
//
boolean needRollback = true;
try {
isInTask = true;
V v = task.run(this);
needRollback = false;
return v;
} catch (Exception e) {
throw e;
} finally {
isInTask = false;
markedForRollback = needRollback;
}
}
public void save() {
if (model != null) {
if (!markedForRollback) {
// Trigger persistent save
model.save();
// Reset modified state
reset();
} else {
log.debug("Will not save session that is marked for rollback");
}
}
}
/**
* <p>
* Closes the current session and discard the changes done during the session.
* </p>
*
* @see #close(boolean)
*/
public void close() {
close(false);
}
/**
* <p>
* Closes the current session and optionally saves its content. If no session is associated then this method has no effects
* and returns false.
* </p>
*
* @param save if the session must be saved
*/
public void close(boolean save) {
if (save) {
save();
}
// Close model
if (model != null) {
model.close();
}
//
configurator.closeContext(save & markedForRollback);
}
}