/**
* 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 org.chromattic.api.ChromatticSession;
import org.chromattic.api.UndeclaredRepositoryException;
import org.exoplatform.commons.chromattic.SessionContext;
import org.exoplatform.commons.chromattic.SynchronizationListener;
import org.exoplatform.commons.chromattic.SynchronizationStatus;
import org.exoplatform.portal.application.PortletPreferences;
import org.exoplatform.portal.config.NoSuchDataException;
import org.exoplatform.portal.pom.data.Mapper;
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.MOPFormatter;
import org.gatein.mop.core.api.ModelImpl;
import org.gatein.mop.core.api.workspace.NavigationImpl;
import org.gatein.mop.core.api.workspace.PageImpl;
import javax.jcr.RepositoryException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public 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;
/** Hack. */
private final Map<String, PortletPreferences> pendingPrefs = new HashMap<String, PortletPreferences>();
/** . */
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
{
modified = getSession().getJCRSession().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 void addPortletPreferences(PortletPreferences prefs)
{
pendingPrefs.put(prefs.getWindowId(), prefs);
}
public PortletPreferences getPortletPreferences(String id)
{
return pendingPrefs.get(id);
}
public Set<PortletPreferences> getPortletPreferences(Site site)
{
Set<PortletPreferences> prefs = new HashSet<PortletPreferences>();
for (Iterator<Map.Entry<String, PortletPreferences>> i = pendingPrefs.entrySet().iterator(); i.hasNext();)
{
Map.Entry<String, PortletPreferences> entry = i.next();
String prefix = Mapper.getOwnerType(site.getObjectType()) + "#" + site.getName() + ":/";
if (entry.getKey().startsWith(prefix))
{
prefs.add(entry.getValue());
i.remove();
}
}
return prefs;
}
public <O extends WorkspaceObject> Iterator<O> findObjects(ObjectType<O> type, ObjectType<? extends Site> siteType,
String ownerId, String title)
{
this.save();
//
String ownerIdChunk = ownerId != null ? new MOPFormatter().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;
if (siteType != null)
{
try
{
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 ''";
}
}
}
else
{
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.toLowerCase() + "%')";
}
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'";
}
}
}
// Temporary work around, to fix in MOP and then remove
ChromatticSession session;
try
{
Field f = ModelImpl.class.getDeclaredField("session");
f.setAccessible(true);
session = (ChromatticSession)f.get(model);
}
catch (Exception e)
{
throw new Error(e);
}
Class<O> mappedClass = (Class<O>)mapping.get(type);
return session.createQueryBuilder(mappedClass).where(statement).get().objects();
}
private final SynchronizationListener listener = new SynchronizationListener()
{
public void beforeSynchronization()
{
}
public void afterSynchronization(SynchronizationStatus status)
{
if (status == SynchronizationStatus.SAVED && staleKeys != null)
{
if (log.isTraceEnabled())
{
log.trace("Session commit about to evict entries " + staleKeys);
}
for (Serializable key : staleKeys)
{
mgr.cacheRemove(key);
}
}
}
};
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;
}
finally
{
isInTask = false;
markedForRollback = needRollback;
}
}
public void save()
{
if (model != null)
{
if (!markedForRollback)
{
model.save();
}
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);
}
}