package com.ecyrd.jspwiki.providers; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.Vector; import com.ecyrd.jspwiki.NoRequiredPropertyException; import com.ecyrd.jspwiki.WikiEngine; import com.ecyrd.jspwiki.WikiPage; import com.perforce.api.Change; import com.perforce.api.Env; import com.perforce.api.FileEntry; import com.perforce.api.Utils; import org.apache.log4j.Category; /** * This class implements a simple P4 file provider. * NOTE: You MUST have the P4 environment configured * in your WEB-INF/jspwiki.propertiessetup and in your path * *
* The P4 file provider extends from the FileSystemProvider, which * means that it provides the pages in the same way. The only difference * is that it implements the version history commands, and also in each * checkin it writes the page to the p4 depot as well. * * This uses the java p4 API P4Package avaliable at: * http://public.perforce.com/public/perforce/api/java/p4package/index.html * I don't really think the API is that clean at all, but it worked so... * * One other idea would be comebine this with VersioningFileProvider * And periodically check things into p4 in the background, or * based on user intiated action * * @author Mark Griffith */ public class P4FileProvider extends FileSystemProvider { private static final Category log = Category.getInstance(P4FileProvider.class); private String requiredP4Props[] = { "p4.executable", "p4.port", "p4.user", "p4.client", "p4.sysroot", "p4.sysdrive" }; Env p4Env = null; public void initialize( Properties props ) throws NoRequiredPropertyException, IOException { log.debug("Initing P4FileProvider"); //Make sure that we get all the p4props that we need setup in the jspwiki.properties file //This will throw an exception if the property doesn't exist for(int i=0;i< requiredP4Props.length;i++) { log.debug(requiredP4Props[i] + " : " + WikiEngine.getRequiredProperty( props, requiredP4Props[i])); } //Pass jspwiki.properties into P4Env object p4Env = new Env(props); super.initialize( props ); } public WikiPage getPageInfo( String page, int version ) throws ProviderException { log.debug("\ngetPageInfo page : " + page + " version : " + version +"\n" + stacktrace2String(new Throwable().getStackTrace())); //If the version is -1 Don't go bother looking in p4 WikiPage info = super.getPageInfo( page, version ); //This method still seems to be called way too many times. //Not sure if I have done something to screw up the caching provider //Or if that is the just the JSPWiki architecure if(info == null) { return null; } String path = getPageDirectory() + File.separator + mangleName(page) + FILE_EXT; log.debug("getPageInfo Path : " + path); try { //These make the p4 code spit out debug messages // if (log.isDebugEnabled()) { // Debug.setDebugLevel(Debug.VERBOSE); // Debug.setLogLevel(Debug.LOG_SPLIT); // } FileEntry p4File = new FileEntry(p4Env,path); //Returns the head revision number for this file. //In otherwords myfile.txt#3 if( version != WikiPageProvider.LATEST_VERSION) { p4File.setHeadRev(version); } p4File.sync(); log.debug("Got p4File version " + p4File.getHeadRev()); //If the file does not exist in p4 then return from disk if(p4File.getHeadRev() == 0) { return info; } //Set the version so that we get the right version from getPageText info.setVersion(p4File.getHeadRev()); info.setLastModified(new Date((p4File.getHeadTime()*1000))); //Rather than create a p4 client for everyone I am just sticking it in the description //its a hack but it works String description = getUserFromDescription(FileEntry.getMostRecentFileLog(p4Env, path).getDescription()); log.debug("-------------------------description : " + description); info.setAuthor(description); } catch(Exception e) { log.warn("Failed to read p4 file info",e); } finally { Utils.cleanUp(); } return info; } public String getPageText( String page, int version ) throws ProviderException { String result = null; log.debug("\n\ngetPageText for version "+version+" of page "+page); // Let parent handle latest fetches, since the FileSystemProvider // can do the file reading just as well. if( version == WikiPageProvider.LATEST_VERSION ) { return super.getPageText( page, version ); } //NOTE markg@bea.com June-7-2003 if your historical overview page //has size() in the page then you have to call getPageText on each version //and count the bites, VERY inefficient. Reccomend taking that out jsp if(log.isDebugEnabled()){ Thread.dumpStack(); } String path = getPageDirectory() + File.separator + mangleName(page) + FILE_EXT; log.debug("getPageText Path : " + path); try { FileEntry p4file = new FileEntry(p4Env, path); p4file.setHeadRev(version); result = p4file.getFileContents(); if(log.isDebugEnabled()){ log.debug("getPageText have text of |\n" + result + "\n|"); } } catch( Exception e ) { log.error("P4 file checkout failed", e); } finally { Utils.cleanUp(); } return result; } /** * Puts the page into p4 and makes sure there is a fresh copy in * the directory as well. */ public void putPageText(WikiPage page, String text ) { String pagename = page.getName(); WikiPage info = null; try { info = super.getPageInfo(page.getName(),WikiPageProvider.LATEST_VERSION); } catch(ProviderException e) { log.error("Couldn't get file page.getName to check for putting", e); } boolean add = info == null; if(log.isDebugEnabled()){ log.debug("Checking in text... for " + page + " version of : " + page.getVersion() + " action : " + ((add) ? "adding" : "updating")); } try { String fileName = getPageDirectory() + File.separator + mangleName(pagename) + FILE_EXT; String author = page.getAuthor(); if( author == null ) author = "unknown"; log.debug("p4 submit fileName : " + fileName); //These make the p4 code spit out debug messages // if (log.isDebugEnabled()) { // Debug.setDebugLevel(Debug.VERBOSE); // Debug.setLogLevel(Debug.LOG_SPLIT); // } Change change = new Change(p4Env); //I really hate this API, the relationship between p4 and and the fileentry is all screwy //I have to reuse env, or else I get a default client env //FileEntry p4File = new FileEntry(p4Env, fileName); change.setDescription("Edit by : |" + author + "| on " + new Date().toString()); change.commit(); if(add){ //write the file to disk super.putPageText(page,text); //open for add //p4File.openForAdd(change); FileEntry.openForAdd(p4Env, fileName, change); } else { //UPDATE noforce nolock FileEntry.openForEdit(p4Env, fileName, false, false, false, change); //Now write the file to disk super.putPageText( page, text ); } change.submit(); } catch( Exception e ) { log.error("P4 checkin failed",e); } finally { Utils.cleanUp(); } } public List getVersionHistory( String page ) { log.debug("Getting P4 version history"); ArrayList list = new ArrayList(); try { String path = getPageDirectory() + File.separator + mangleName(page) + FILE_EXT; log.debug("getVersionHistory path : " + path); Vector files = FileEntry.getFileLog(p4Env, path); for(int i = 0; i < files.size(); i++) { WikiPage info = new WikiPage( page ); FileEntry p4File = (FileEntry)files.get(i); info.setVersion(p4File.getHeadRev()); info.setLastModified(new Date(p4File.getHeadTime())); log.debug("User : " + getUserFromDescription(p4File.getDescription())); info.setAuthor(getUserFromDescription(p4File.getDescription())); list.add(info); } } catch( Exception e ) { log.error( "p4 file hisory failed", e ); } finally { Utils.cleanUp(); } return list; } //Rather than create a p4 client for everyone I am just sticking it in the description //its a hack but it works private String getUserFromDescription(String description) { if(description.indexOf("|") < description.lastIndexOf("|")) { try { return description.substring(description.indexOf("|")+1, description.lastIndexOf("|")); }catch(StringIndexOutOfBoundsException sioobe) { log.error( "Error getting user from description of : " + description, sioobe ); } } return "unknown"; } private String stacktrace2String(StackTraceElement[] selts) { StringBuffer sbuf = new StringBuffer(); for (int i = 0; i < selts.length; i++) { sbuf.append(selts[i]); sbuf.append('\n'); } return sbuf.toString(); } }