/*
Copyright (c) 2003 eInnovation Inc. All rights reserved
This library 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 library 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.
*/
package com.openedit.page.manage;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.entermedia.cache.CacheManager;
import org.openedit.repository.CompoundRepository;
import org.openedit.repository.ContentItem;
import org.openedit.repository.OutputStreamItem;
import org.openedit.repository.ReaderItem;
import org.openedit.repository.RepositoryException;
import com.openedit.OpenEditException;
import com.openedit.PageAccessListener;
import com.openedit.WebPageRequest;
import com.openedit.page.Page;
import com.openedit.page.PageSettings;
import com.openedit.users.User;
import com.openedit.util.PathUtilities;
/**
* The PageManager is a central access point for locating pages. Pages are
* loaded and cached automatically. The cache will check the file's last
* modification time and will update if the stored time does not match the file
* system's time.
*
* @author Matt Avery, mavery@einnovation.com
*/
public class PageManager
{
private static Log log = LogFactory.getLog( PageManager.class );
protected Map fieldPageAccessListeners;
protected CacheManager fieldCacheManager;
protected CompoundRepository fieldRepository;
protected PageSettingsManager fieldSettingsManager;
private static final String CACHEID = PageManager.class.getName();
public PageManager()
{
log.debug("create page manager instance");
}
public Page getPage(String inPath, WebPageRequest inReq) throws OpenEditException
{
//this gets the page for this user
Boolean checkCurrent = (Boolean)inReq.getPageValue("reloadpages");
if( checkCurrent == null)
{
checkCurrent = Boolean.FALSE;
}
//boolean checkCurrent = Boolean.parseBoolean( inReq.findValue("reload") );
Page real = getPage(inPath, checkCurrent);
return getPage(real,checkCurrent,inReq);
}
/**
* This checks for pages in this order:
* 1. Draft page in the language directory
* 2. Page in the language directory
* 3. Draft in the real directory
* 4. Original file in the real directory
* @param inPage
* @param inReq
* @return
* @throws OpenEditException
*/
public Page getPage(Page inPage, WebPageRequest inReq) throws OpenEditException
{
//boolean checkCurrent = inReq.getUser() != null; //this no longer works since we do not have a user yet
Boolean checkCurrent = (Boolean)inReq.getPageValue("reloadpages");
if( checkCurrent == null)
{
checkCurrent = Boolean.FALSE;
}
return getPage( inPage, checkCurrent, inReq);
}
public Page getPage(Page inPage, boolean inCheckCurrent, WebPageRequest inReq) throws OpenEditException
{
String inPath = inPage.getPath();
//log.info("getPage" + inPath);
boolean useDraft = true;
User user = inReq.getUser();
if ( user == null )
{
useDraft = false;
}
if ( useDraft && !user.hasProperty("oe.edit.draftmode") )
{
useDraft = false;
}
if( useDraft && inPage.isDraft() )
{
useDraft = false;
}
if( useDraft )
{
String draftedits = inPage.get("oe.edit.draftedits"); //This is the opposite of oe.edit.directedits
if ( draftedits != null && !Boolean.parseBoolean(draftedits) )
{
useDraft = false;
}
}
boolean multipleLang = false;
String savein = inReq.getPageProperty("usemultiplelanguages");
if ( savein != null )
{
multipleLang = Boolean.parseBoolean(savein);
}
String selectedcode = inReq.getLanguage();
String rootdir = "/translations/" + selectedcode; //TODO: Make configurable
if( multipleLang )
{
if( selectedcode == null || selectedcode.equals("default") )
{
multipleLang = false;
}
if( inPath.startsWith("/translations/") )
{
//strip off the begining
rootdir = inPath.substring(0,rootdir.length());
inPath = inPath.substring(rootdir.length(),inPath.length());
multipleLang = true;
}
}
Page foundPage = null;
if ( useDraft)
{
String dp = PathUtilities.createDraftPath(inPath );
if( multipleLang )
{
Page translated = getPage( rootdir + dp,inCheckCurrent);
if( translated.exists() ) //Does the draft exists?
{
foundPage = translated;
}
else
{
translated = getPage( rootdir + inPath, inCheckCurrent); //Does the page exists in the /translations directory?
if( translated.exists() )
{
foundPage = translated;
}
else
{
//Does the page exists in the home directory
//log.info("trans oath " + inPath);
}
}
}
if( foundPage == null )
{
if( getRepository().doesExist(dp) )
{
Page draft = getPage(dp,inCheckCurrent);
foundPage = draft;
}
}
}
else if( multipleLang )
{
Page translated = getPage( rootdir + inPath,inCheckCurrent);
if( translated.exists() )
{
foundPage = translated;
}
}
if( foundPage == null)
{
return inPage;
}
else
{
return foundPage;
}
}
public Page getPage( String inPath ) throws OpenEditException
{
return getPage(inPath,false);
}
/**
* Get a Page instance from the given path. If no page can be found then
* this method will throw a FileNotFoundException.
*
* TODO: This method needs to be smarter. If I request page.html and I have
* content page.xml, I still need to return a Page object with both
* requested mime-type (text/html) and content mime-type (text/xml)
*
* @param path
* The page path
*
* @return The Page
*
* @throws OpenEditException
* Any Exception
*/
public Page getPage( String inPath, boolean inCheckDates) throws OpenEditException
{
if ( inPath == null)
{
return null;
}
String fullPath = PathUtilities.buildRelative( inPath, "/" );
Page page = (Page) getCacheManager().get( CACHEID, fullPath );
if( page != null && !inCheckDates)
{
return page;
}
boolean reloadPage = false;
if ( page != null )
{
if ( originalPathChanged(page) )
{
//if the fullpath alternative has been added then we need to blow away this page and its settings
reloadPage = true;
}
else if ( !inCheckDates || page.isCurrent() )
{
firePageRequested( page );
return page;
}
else
{
reloadPage = true;
}
}
synchronized( getCacheManager() )
{ //lock down the config until we can configure the thing
page = (Page) getCacheManager().get( CACHEID, fullPath );
if ( page == null || reloadPage ) //check for other threads that where waiting
{
if ( reloadPage)
{
getPageSettingsManager().clearCache(fullPath); //in case alternative file shows up
}
page = createPage( fullPath );
getCacheManager().put(CACHEID, fullPath, page );
firePageRequested( page );
}
}
return page;
}
private boolean originalPathChanged(Page inPage) throws OpenEditException
{
//we only need to check for changes when we are using an alternative content path
if( inPage.getAlternateContentPath() != null)
{
//go check the original path to make sure it has not appeared or been removed
boolean doesExist = getRepository().doesExist( inPage.getPath() );
boolean changed = (doesExist != inPage.getPageSettings().isOriginalyExistedContentPath() );
return changed;
}
return false;
}
protected Page createPage( String inPath ) throws OpenEditException
{
PageSettings settings = getPageSettingsManager().getPageSettings( inPath );
Page page = new Page( inPath, settings );
populatePage( page );
return page;
}
protected void populatePage( Page inPage ) throws OpenEditException
{
ContentItem revision = getContentRevision( inPage );
inPage.setContentItem( revision );
}
protected ContentItem getContentRevision( Page inPage ) throws RepositoryException
{
String path = inPage.getPath();
if (inPage.getAlternateContentPath() != null) //TODO: This should be done in the settings object
{
path = inPage.getAlternateContentPath();
}
ContentItem revision = getRepository().getStub( path );
return revision;
}
public PageSettingsManager getPageSettingsManager()
{
return fieldSettingsManager;
}
public void setPageSettingsManager( PageSettingsManager inManager)
{
fieldSettingsManager = inManager;
}
public void copyPage( Page inSourcePage, Page inDestinationPage ) throws RepositoryException
{
if (inSourcePage.exists())
{
String makeversion = inDestinationPage.getProperty("makeversion");
if (makeversion != null)
{
boolean ver = Boolean.parseBoolean(makeversion);
inDestinationPage.getContentItem().setMakeVersion(ver);
}
ContentItem item = inDestinationPage.getContentItem();
item.setPath(inDestinationPage.getPath());
getRepository().copy( inSourcePage.getContentItem(), item);
}
else
{
throw new RepositoryException("No such page to copy " + inSourcePage);
}
//If we take xconfs when we copy the we best keep the contentfile set
/* if (inSourcePage.getPageSettings().exists())
{
//TODO: Flatten down xconf a little
getRepository().copy( inSourcePage.getPageSettings().getXConf(),
inDestinationPage.getPageSettings().getXConf() );
//
}
*/ firePageAdded( inDestinationPage );
}
public void movePage( Page inSource, Page inDestination ) throws RepositoryException
{
if (inSource.exists())
{
ContentItem item = inDestination.getContentItem(); //Use new path
item.setPath(inDestination.getPath());
String makeversion = inDestination.getProperty("makeversion");
item.setMakeVersion(Boolean.parseBoolean(makeversion));
getRepository().move( inSource.getContentItem(), item);
}
else
{
throw new RepositoryException("No such page to move " + inSource);
}
firePageRemoved( inSource );
firePageAdded( inDestination ); //this event will invalidate the folder in the file manager
}
public void removePage( Page inPage ) throws OpenEditException
{
String makeversion = inPage.getProperty("makeversion");
if (makeversion != null)
{
boolean ver = Boolean.parseBoolean(makeversion);
inPage.getPageSettings().getXConf().setMakeVersion(ver);
inPage.getContentItem().setMakeVersion(ver);
}
inPage.getContentItem().setPath(inPage.getPath());
getRepository().remove( inPage.getContentItem() );
//getRepository().remove( inPage.getPageSettings().getXConf() );
getCacheManager().remove( CACHEID, inPage.getPath() );
firePageRemoved( inPage );
}
public void putPage( Page inPage ) throws OpenEditException
{
//Kind of hackish. Guess we need to look on the disk to be sure but that could be slow
ContentItem oldItem = inPage.getContentItem();
boolean existing = oldItem.exists();
String makeversion = inPage.getProperty("makeversion");
if (makeversion != null)
{
boolean ver = Boolean.parseBoolean(makeversion);
oldItem.setMakeVersion(ver);
}
getRepository().put( oldItem );
clearCache(inPage);
//we want to get the recent version back from disk
if ( oldItem.isMakeVersion() && oldItem.lastModified() == null ) //might be a StringItem for example
{
//Load up a fresh item from the disk drive since we uploaded
ContentItem reloadedItem = getRepository().get( inPage.getPath() );
if ( oldItem.getMessage() != null && reloadedItem.getMessage() == null )
{
reloadedItem.setAuthor(oldItem.getAuthor());
reloadedItem.setMessage(oldItem.getMessage());
reloadedItem.setType(oldItem.getType());
reloadedItem.setVersion(oldItem.getVersion());
}
reloadedItem.setMakeVersion(oldItem.isMakeVersion());
inPage.setContentItem( reloadedItem );
}
if (existing)
{
firePageModified( inPage );
}
else
{
firePageAdded( inPage );
}
}
public void saveSettings(Page inPage)
{
getPageSettingsManager().saveSetting(inPage.getPageSettings());
firePageModified(inPage);
clearCache(inPage);
}
protected CacheManager getCacheManager()
{
return fieldCacheManager;
}
public void setCacheManager(CacheManager inCacheManager)
{
fieldCacheManager = inCacheManager;
}
public CompoundRepository getRepositoryManager()
{
return fieldRepository;
}
public CompoundRepository getRepository()
{
return getRepositoryManager();
}
public void setRepository( CompoundRepository repository )
{
fieldRepository = repository;
}
public void firePageAdded( Page inPage )
{
for ( Iterator iter = getPageAccessListeners().keySet().iterator(); iter.hasNext(); )
{
PageAccessListener listener = (PageAccessListener) iter.next();
listener.pageAdded( inPage );
}
}
public void firePageModified( Page page )
{
for ( Iterator iter = getPageAccessListeners().keySet().iterator(); iter.hasNext(); )
{
PageAccessListener listener = (PageAccessListener) iter.next();
listener.pageModified( page );
}
}
public void firePageRemoved( Page inPage )
{
for ( Iterator iter = getPageAccessListeners().keySet().iterator(); iter.hasNext(); )
{
PageAccessListener listener = (PageAccessListener) iter.next();
listener.pageRemoved( inPage );
}
}
public void firePageRequested( Page inPage )
{
if( getPageAccessListeners().size() > 4) //Our defaults dont count since they dont do anything
{
for ( Iterator iter = getPageAccessListeners().keySet().iterator(); iter.hasNext(); )
{
PageAccessListener listener = (PageAccessListener) iter.next();
listener.pageRequested( inPage );
}
}
}
protected Map getPageAccessListeners()
{
if (fieldPageAccessListeners == null)
{
//HARD means even if the object goes out of scope we still keep it in the hashmap
//until the memory runs low then things get dumped randomly
fieldPageAccessListeners = new HashMap();
}
return fieldPageAccessListeners;
}
public void setSettingsManager( PageSettingsManager metaDataConfigurator )
{
fieldSettingsManager = metaDataConfigurator;
}
public void addPageAccessListener( PageAccessListener inListener )
{
getPageAccessListeners().put( inListener, this );
}
public void removePageAccessListener( PageAccessListener inListener )
{
getPageAccessListeners().remove( inListener );
}
public void clearCache()
{
synchronized( getCacheManager() )
{
getCacheManager().clear(CACHEID);
getPageSettingsManager().clearCache();
}
}
/**
* @param inPage
*/
public void clearCache(Page inPage)
{
clearCache(inPage.getPath());
}
public void clearCache(String inPath)
{
if( inPath != null )
{
synchronized( getCacheManager() )
{
getCacheManager().remove(CACHEID, inPath);
getPageSettingsManager().clearCache(inPath);
}
}
}
public void saveContent(Page inPage, User inUser, String inContent, String inMessage) throws OpenEditException
{
saveContent(inPage, inUser, new StringReader(inContent), inMessage);
}
public void saveContent(Page inPage, User inUser, Reader inReader, String inMessage)
{
ReaderItem item = new ReaderItem(inPage.getPath(),inReader,inPage.getCharacterEncoding());
item.setMessage(inMessage);
item.setAuthor(inUser.getUserName());
inPage.setContentItem(item);
putPage(inPage);
}
public OutputStream saveToStream(Page inPage, User inUser, String inMessage)
{
OutputStreamItem item = new OutputStreamItem(inPage.getPath());
item.setMessage(inMessage);
if(inUser != null){
//could be null
item.setAuthor(inUser.getUserName());
}
boolean previous = inPage.getContentItem().isMakeVersion();
item.setMakeVersion(previous);
inPage.setContentItem(item);
putPage(inPage); //This sets the outputstream for us
return item.getOutputStream();
}
public OutputStream saveToStream(Page inPage)
{
OutputStreamItem item = new OutputStreamItem(inPage.getPath());
inPage.setContentItem(item);
putPage(inPage); //This sets the outputstream for us
return item.getOutputStream();
}
public List getChildrenPaths(String inUrl) throws RepositoryException
{
return getChildrenPaths(inUrl, false);
}
public List getChildrenPathsSorted(String inUrl) throws RepositoryException
{
List paths = getChildrenPaths(inUrl, false);
Collections.sort(paths, new Comparator()
{
public int compare(Object inO1, Object inO2)
{
return inO1.toString().toLowerCase().compareTo(inO2.toString().toLowerCase());
}
});
return paths;
}
/**
* @deprecated see getChildrenPaths
* @param inUrl
* @return
* @throws RepositoryException
*/
public List getChildrenNames(String inUrl) throws RepositoryException
{
return getChildrenPaths(inUrl);
}
public ContentItem getLatestVersion(String inPath) throws RepositoryException
{
return getRepository().getLastVersion(inPath);
}
public List getVersions(String inPath) throws RepositoryException
{
return getRepository().getVersions(inPath);
}
public List getChildrenPaths(String inPath, boolean inIncludeFallBack)
{
List all = getRepository().getChildrenNames(inPath);
if( inIncludeFallBack)
{
PageSettings settings = getPageSettingsManager().getPageSettings(inPath);
settings = settings.getFallback();
while( settings != null)
{
String dirparent = PathUtilities.extractDirectoryPath(settings.getPath());
List morechildren = getRepository().getChildrenNames(dirparent );
all.addAll(morechildren);
settings = settings.getFallback();
}
}
return all;
}
}