/* JSPWiki - a JSP-based WikiWiki clone. Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi) This program 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 program 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 program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.ecyrd.jspwiki; import java.io.*; import java.security.Principal; import java.util.*; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.time.StopWatch; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import com.ecyrd.jspwiki.attachment.Attachment; import com.ecyrd.jspwiki.attachment.AttachmentManager; import com.ecyrd.jspwiki.auth.AuthenticationManager; import com.ecyrd.jspwiki.auth.AuthorizationManager; import com.ecyrd.jspwiki.auth.UserManager; import com.ecyrd.jspwiki.auth.acl.AclManager; import com.ecyrd.jspwiki.auth.acl.DefaultAclManager; import com.ecyrd.jspwiki.auth.authorize.GroupManager; import com.ecyrd.jspwiki.auth.user.UserDatabase; import com.ecyrd.jspwiki.diff.DifferenceManager; import com.ecyrd.jspwiki.filters.FilterException; import com.ecyrd.jspwiki.filters.FilterManager; import com.ecyrd.jspwiki.parser.JSPWikiMarkupParser; import com.ecyrd.jspwiki.parser.MarkupParser; import com.ecyrd.jspwiki.plugin.PluginManager; import com.ecyrd.jspwiki.providers.ProviderException; import com.ecyrd.jspwiki.providers.WikiPageProvider; import com.ecyrd.jspwiki.render.RenderingManager; import com.ecyrd.jspwiki.rss.RSSGenerator; import com.ecyrd.jspwiki.search.SearchManager; import com.ecyrd.jspwiki.ui.EditorManager; import com.ecyrd.jspwiki.ui.TemplateManager; import com.ecyrd.jspwiki.url.URLConstructor; import com.ecyrd.jspwiki.util.ClassUtil; /** * Provides Wiki services to the JSP page. * *

* This is the main interface through which everything should go. * *

* Using this class: Always get yourself an instance from JSP page * by using the WikiEngine.getInstance() method. Never create a new * WikiEngine() from scratch, unless you're writing tests. *

* There's basically only a single WikiEngine for each web application, and * you should always get it using the WikiEngine.getInstance() method. * * @author Janne Jalkanen */ public class WikiEngine { private static final Logger log = Logger.getLogger(WikiEngine.class); /** True, if log4j has been configured. */ // FIXME: If you run multiple applications, the first application // to run defines where the log goes. Not what we want. private static boolean c_configured = false; /** Stores properties. */ private Properties m_properties; /** The web.xml parameter that defines where the config file is to be found. * If it is not defined, uses the default as defined by DEFAULT_PROPERTYFILE. * {@value jspwiki.propertyfile} */ public static final String PARAM_PROPERTYFILE = "jspwiki.propertyfile"; /** Property for default WikiPage file extension **/ public static final String PROP_WIKIPAGEEXT = "jspwiki.fileSystemProvider.pageExtension"; /** Property for application name */ public static final String PROP_APPNAME = "jspwiki.applicationName"; /** Property start for any interwiki reference. */ public static final String PROP_INTERWIKIREF = "jspwiki.interWikiRef."; /** If true, then the user name will be stored with the page data.*/ public static final String PROP_STOREUSERNAME= "jspwiki.storeUserName"; /** Define the used encoding. Currently supported are ISO-8859-1 and UTF-8 */ public static final String PROP_ENCODING = "jspwiki.encoding"; /** The name for the base URL to use in all references. */ public static final String PROP_BASEURL = "jspwiki.baseURL"; public static final String PROP_REFSTYLE = "jspwiki.referenceStyle"; /** Property name for the "spaces in titles" -hack. */ public static final String PROP_BEAUTIFYTITLE = "jspwiki.breakTitleWithSpaces"; /** Property name for where the jspwiki work directory should be. If not specified, reverts to ${java.tmpdir}. */ public static final String PROP_WORKDIR = "jspwiki.workDir"; /** The name of the cookie that gets stored to the user browser. */ public static final String PREFS_COOKIE_NAME = "JSPWikiUserProfile"; /** Property name for the "match english plurals" -hack. */ public static final String PROP_MATCHPLURALS = "jspwiki.translatorReader.matchEnglishPlurals"; /** Property name for the template that is used. */ public static final String PROP_TEMPLATEDIR = "jspwiki.templateDir"; /** Property name for the default front page. */ public static final String PROP_FRONTPAGE = "jspwiki.frontPage"; /** Property name for setting the url generator instance */ public static final String PROP_URLCONSTRUCTOR = "jspwiki.urlConstructor"; private static final String PROP_SPECIALPAGE = "jspwiki.specialPage."; /** If this property is set to false, all filters are disabled when translating. */ public static final String PROP_RUNFILTERS = "jspwiki.runFilters"; /** Path to the default property file. * {@value /WEB_INF/jspwiki.properties} */ public static final String DEFAULT_PROPERTYFILE = "/WEB-INF/jspwiki.properties"; /** Does the work in renaming pages. */ private PageRenamer m_pageRenamer = null; /** * Contains the default properties for JSPWiki. */ private static final String[] DEFAULT_PROPERTIES = { "jspwiki.specialPage.Login", "Login.jsp", "jspwiki.specialPage.Logout", "Logout.jsp", "jspwiki.specialPage.CreateGroup", "NewGroup.jsp", "jspwiki.specialPage.CreateProfile", "Register.jsp", "jspwiki.specialPage.EditProfile", "UserPreferences.jsp", "jspwiki.specialPage.Preferences", "UserPreferences.jsp", "jspwiki.specialPage.Search", "Search.jsp", "jspwiki.specialPage.FindPage", "FindPage.jsp"}; /** Stores an internal list of engines per each ServletContext */ private static Hashtable c_engines = new Hashtable(); /** Should the user info be saved with the page data as well? */ private boolean m_saveUserInfo = true; /** If true, uses UTF8 encoding for all data */ private boolean m_useUTF8 = true; /** If true, we'll also consider english plurals (+s) a match. */ private boolean m_matchEnglishPlurals = true; /** Stores the base URL. */ private String m_baseURL; /** Store the file path to the basic URL. When we're not running as a servlet, it defaults to the user's current directory. */ private String m_rootPath = System.getProperty("user.dir"); /** Stores references between wikipages. */ private ReferenceManager m_referenceManager = null; /** Stores the Plugin manager */ private PluginManager m_pluginManager; /** Stores the Variable manager */ private VariableManager m_variableManager; /** Stores the Attachment manager */ private AttachmentManager m_attachmentManager = null; /** Stores the Page manager */ private PageManager m_pageManager = null; /** Stores the authorization manager */ private AuthorizationManager m_authorizationManager = null; /** Stores the authentication manager.*/ private AuthenticationManager m_authenticationManager = null; /** Stores the ACL manager. */ private AclManager m_aclManager = null; private TemplateManager m_templateManager = null; /** Does all our diffs for us. */ private DifferenceManager m_differenceManager; /** Handlers page filters. */ private FilterManager m_filterManager; /** Stores the Search manager */ private SearchManager m_searchManager = null; private UserManager m_userManager; private RenderingManager m_renderingManager; private EditorManager m_editorManager; /** Constructs URLs */ private URLConstructor m_urlConstructor; /** Generates RSS feed when requested. */ private RSSGenerator m_rssGenerator; /** Stores the relative URL to the global RSS feed. */ private String m_rssURL; /** Store the ServletContext that we're in. This may be null if WikiEngine is not running inside a servlet container (i.e. when testing). */ private ServletContext m_servletContext = null; /** If true, all titles will be cleaned. */ private boolean m_beautifyTitle = false; /** Stores the template path. This is relative to "templates". */ private String m_templateDir; /** The default front page name. Defaults to "Main". */ private String m_frontPage; /** The time when this engine was started. */ private Date m_startTime; /** The location where the work directory is. */ private String m_workDir; /** Each engine has their own application id. */ private String m_appid = ""; private boolean m_isConfigured = false; // Flag. /** * Gets a WikiEngine related to this servlet. Since this method * is only called from JSP pages (and JspInit()) to be specific, * we throw a RuntimeException if things don't work. * * @param config The ServletConfig object for this servlet. * * @return A WikiEngine instance. * @throws InternalWikiException in case something fails. This * is a RuntimeException, so be prepared for it. */ // FIXME: It seems that this does not work too well, jspInit() // does not react to RuntimeExceptions, or something... public static synchronized WikiEngine getInstance( ServletConfig config ) throws InternalWikiException { return( getInstance( config.getServletContext(), null ) ); } /** * Gets a WikiEngine related to the servlet. Works like getInstance(ServletConfig), * but does not force the Properties object. This method is just an optional way * of initializing a WikiEngine for embedded JSPWiki applications; normally, you * should use getInstance(ServletConfig). * * @param config The ServletConfig of the webapp servlet/JSP calling this method. * @param props A set of properties, or null, if we are to load JSPWiki's default * jspwiki.properties (this is the usual case). */ public static synchronized WikiEngine getInstance( ServletConfig config, Properties props ) { return( getInstance( config.getServletContext(), null ) ); } /** * Gets a WikiEngine related to the servlet. Works just like getInstance( ServletConfig ) * * @param context The ServletContext of the webapp servlet/JSP calling this method. * @param props A set of properties, or null, if we are to load JSPWiki's default * jspwiki.properties (this is the usual case). */ // FIXME: Potential make-things-easier thingy here: no need to fetch the wikiengine anymore // Wiki.jsp.jspInit() [really old code]; it's probably even faster to fetch it // using this method every time than go to pageContext.getAttribute(). public static synchronized WikiEngine getInstance( ServletContext context, Properties props ) throws InternalWikiException { String appid = Integer.toString(context.hashCode()); //FIXME: Kludge, use real type. context.log( "Application "+appid+" requests WikiEngine."); WikiEngine engine = (WikiEngine) c_engines.get( appid ); if( engine == null ) { context.log(" Assigning new log to "+appid); try { if( props == null ) props = loadWebAppProps( context ); engine = new WikiEngine( context, appid, props ); } catch( Exception e ) { context.log( "ERROR: Failed to create a Wiki engine: "+e.getMessage() ); throw new InternalWikiException( "No wiki engine, check logs." ); } c_engines.put( appid, engine ); } return engine; } /** * Instantiate the WikiEngine using a given set of properties. * Use this constructor for testing purposes only. */ public WikiEngine( Properties properties ) throws WikiException { initialize( properties ); } /** * Loads the webapp properties based on servlet context information. * Returns a Properties object containing the settings, or null if unable * to load it. (The default file is WEB-INF/jspwiki.properties, and can * be overridden by setting PARAM_PROPERTYFILE in the server or webapp * configuration.) */ private static Properties loadWebAppProps( ServletContext context ) { String propertyFile = context.getInitParameter(PARAM_PROPERTYFILE); InputStream propertyStream = null; try { // // Figure out where our properties lie. // if( propertyFile == null ) { context.log("No "+PARAM_PROPERTYFILE+" defined for this context, using default from "+DEFAULT_PROPERTYFILE); // Use the default property file. propertyStream = context.getResourceAsStream(DEFAULT_PROPERTYFILE); } else { context.log("Reading properties from "+propertyFile+" instead of default."); propertyStream = new FileInputStream( new File(propertyFile) ); } if( propertyStream == null ) { throw new WikiException("Property file cannot be found!"+propertyFile); } Properties props = new Properties( TextUtil.createProperties( DEFAULT_PROPERTIES ) ); props.load( propertyStream ); return( props ); } catch( Exception e ) { context.log( Release.APPNAME+": Unable to load and setup properties from jspwiki.properties. "+e.getMessage() ); } finally { try { propertyStream.close(); } catch( IOException e ) { context.log("Unable to close property stream - something must be seriously wrong."); } } return( null ); } /** * Instantiate using this method when you're running as a servlet and * WikiEngine will figure out where to look for the property * file. * Do not use this method - use WikiEngine.getInstance() instead. */ protected WikiEngine( ServletContext context, String appid, Properties props ) throws WikiException { m_servletContext = context; m_appid = appid; try { // // Note: May be null, if JSPWiki has been deployed in a WAR file. // m_rootPath = context.getRealPath("/"); initialize( props ); log.info("Root path for this Wiki is: '"+m_rootPath+"'"); } catch( Exception e ) { context.log( Release.APPNAME+": Unable to load and setup properties from jspwiki.properties. "+e.getMessage() ); } } /** * Does all the real initialization. */ private void initialize( Properties props ) throws WikiException { m_startTime = new Date(); m_properties = props; // // Initialized log4j. However, make sure that // we don't initialize it multiple times. Also, if // all of the log4j statements have been removed from // the property file, we do not do any property setting // either.q // if( !c_configured ) { if( props.getProperty("log4j.rootCategory") != null ) { PropertyConfigurator.configure( props ); } c_configured = true; } log.info("*******************************************"); log.info("JSPWiki "+Release.VERSTR+" starting. Whee!"); log.debug("Configuring WikiEngine..."); // // Create and find the default working directory. // m_workDir = TextUtil.getStringProperty( props, PROP_WORKDIR, null ); if( m_workDir == null ) { m_workDir = System.getProperty("java.io.tmpdir", "."); m_workDir += File.separator+Release.APPNAME+"-"+m_appid; } try { File f = new File( m_workDir ); f.mkdirs(); // // A bunch of sanity checks // if( !f.exists() ) throw new WikiException("Work directory does not exist: "+m_workDir); if( !f.canRead() ) throw new WikiException("No permission to read work directory: "+m_workDir); if( !f.canWrite() ) throw new WikiException("No permission to write to work directory: "+m_workDir); if( !f.isDirectory() ) throw new WikiException("jspwiki.workDir does not point to a directory: "+m_workDir); } catch( SecurityException e ) { log.fatal("Unable to find or create the working directory: "+m_workDir,e); throw new IllegalArgumentException("Unable to find or create the working dir: "+m_workDir); } log.info("JSPWiki working directory is '"+m_workDir+"'"); m_saveUserInfo = TextUtil.getBooleanProperty( props, PROP_STOREUSERNAME, m_saveUserInfo ); m_useUTF8 = "UTF-8".equals( TextUtil.getStringProperty( props, PROP_ENCODING, "ISO-8859-1" ) ); m_baseURL = TextUtil.getStringProperty( props, PROP_BASEURL, "" ); m_beautifyTitle = TextUtil.getBooleanProperty( props, PROP_BEAUTIFYTITLE, m_beautifyTitle ); m_matchEnglishPlurals = TextUtil.getBooleanProperty( props, PROP_MATCHPLURALS, m_matchEnglishPlurals ); m_templateDir = TextUtil.getStringProperty( props, PROP_TEMPLATEDIR, "default" ); m_frontPage = TextUtil.getStringProperty( props, PROP_FRONTPAGE, "Main" ); // // Initialize the important modules. Any exception thrown by the // managers means that we will not start up. // try { Class urlclass = ClassUtil.findClass( "com.ecyrd.jspwiki.url", TextUtil.getStringProperty( props, PROP_URLCONSTRUCTOR, "DefaultURLConstructor" ) ); m_urlConstructor = (URLConstructor) urlclass.newInstance(); m_urlConstructor.initialize( this, props ); m_pageManager = new PageManager( this, props ); m_pluginManager = new PluginManager( props ); m_differenceManager = new DifferenceManager( this, props ); m_attachmentManager = new AttachmentManager( this, props ); m_variableManager = new VariableManager( props ); m_filterManager = new FilterManager( this, props ); m_renderingManager = new RenderingManager(); m_renderingManager.initialize( this, props ); m_searchManager = new SearchManager( this, props ); m_authenticationManager = new AuthenticationManager(); m_authorizationManager = new AuthorizationManager(); m_userManager = new UserManager(); m_editorManager = new EditorManager(); m_editorManager.initialize( this, props ); // Initialize the authentication, authorization, user and acl managers m_authenticationManager.initialize( this, props ); m_authorizationManager.initialize( this, props ); m_userManager.initialize( this, props ); // m_groupManager = getGroupManager(); m_aclManager = getAclManager(); // // ReferenceManager has the side effect of loading all // pages. Therefore after this point, all page attributes // are available. // initReferenceManager(); m_templateManager = new TemplateManager( this, props ); // // Hook the different manager routines into the system. // getFilterManager().addPageFilter(m_referenceManager, -1000 ); getFilterManager().addPageFilter(m_searchManager, -1001 ); } catch( Exception e ) { // RuntimeExceptions may occur here, even if they shouldn't. log.fatal( "Failed to start managers.", e ); throw new WikiException( "Failed to start managers: "+e.getMessage() ); } // // Initialize the good-to-have-but-not-fatal modules. // try { if( TextUtil.getBooleanProperty( props, RSSGenerator.PROP_GENERATE_RSS, false ) ) { m_rssGenerator = new RSSGenerator( this, props ); } m_pageRenamer = new PageRenamer( this, props ); } catch( Exception e ) { log.error( "Unable to start RSS generator - JSPWiki will still work, "+ "but there will be no RSS feed.", e ); } // FIXME: I wonder if this should be somewhere else. if( m_rssGenerator != null ) { new RSSThread().start(); } log.info("WikiEngine configured."); m_isConfigured = true; } /** * Initializes the reference manager. Scans all existing WikiPages for * internal links and adds them to the ReferenceManager object. */ public void initReferenceManager() { m_pluginManager.setInitStage( true ); try { ArrayList pages = new ArrayList(); pages.addAll( m_pageManager.getAllPages() ); pages.addAll( m_attachmentManager.getAllAttachments() ); // Build a new manager with default key lists. if( m_referenceManager == null ) { m_referenceManager = new ReferenceManager( this ); m_referenceManager.initialize( pages ); } } catch( ProviderException e ) { log.fatal("PageProvider is unable to list pages: ", e); } m_pluginManager.setInitStage( false ); } /** * Throws an exception if a property is not found. * * @param props A set of properties to search the key in. * @param key The key to look for. * @return The required property * * @throws NoRequiredPropertyException If the search key is not * in the property set. */ // FIXME: Should really be in some util file. public static String getRequiredProperty( Properties props, String key ) throws NoRequiredPropertyException { String value = TextUtil.getStringProperty( props, key, null ); if( value == null ) { throw new NoRequiredPropertyException( "Required property not found", key ); } return value; } /** * Internal method for getting a property. This is used by the * TranslatorReader for example. */ public Properties getWikiProperties() { return m_properties; } /** * Returns the JSPWiki working directory. * @since 2.1.100 */ public String getWorkDir() { return m_workDir; } /** * Don't use. * @since 1.8.0 * @deprecated */ public String getPluginSearchPath() { // FIXME: This method should not be here, probably. return TextUtil.getStringProperty( m_properties, PluginManager.PROP_SEARCHPATH, null ); } /** * Returns the current template directory. * * @since 1.9.20 */ public String getTemplateDir() { return m_templateDir; } public TemplateManager getTemplateManager() { return m_templateManager; } /** * Returns the base URL. Always prepend this to any reference * you make. * * @since 1.6.1 */ public String getBaseURL() { return m_baseURL; } /** * Returns the moment when this engine was started. * * @since 2.0.15. */ public Date getStartTime() { return m_startTime; } /** * Returns the basic absolute URL to a page, without any modifications. * You may add any parameters to this. This is a convinience method. * * @since 2.0.3 */ public String getViewURL( String pageName ) { return m_urlConstructor.makeURL( WikiContext.VIEW, pageName, true, null ); } /** * Returns the basic URL to an editor. * @deprecated * * @since 2.0.3 */ public String getEditURL( String pageName ) { return m_urlConstructor.makeURL( WikiContext.EDIT, pageName, false, null ); } /** * Returns the basic attachment URL. * @since 2.0.42. * @deprecated */ public String getAttachmentURL( String attName ) { return m_urlConstructor.makeURL( WikiContext.ATTACH, attName, false, null ); } /** * Returns an URL if a WikiContext is not available. * @param context The WikiContext (VIEW, EDIT, etc...) * @param pageName Name of the page, as usual * @param params List of parameters. May be null, if no parameters. * @param absolute If true, will generate an absolute URL regardless of properties setting. */ public String getURL( String context, String pageName, String params, boolean absolute ) { return m_urlConstructor.makeURL( context, pageName, absolute, params ); } /** * Returns the default front page, if no page is used. */ public String getFrontPage() { return m_frontPage; } /** * Returns the ServletContext that this particular WikiEngine was * initialized with. It may return null, if the WikiEngine is not * running inside a servlet container! * * @since 1.7.10 * @return ServletContext of the WikiEngine, or null. */ public ServletContext getServletContext() { return m_servletContext; } /** * This is a safe version of the Servlet.Request.getParameter() routine. * Unfortunately, the default version always assumes that the incoming * character set is ISO-8859-1, even though it was something else. * This means that we need to make a new string using the correct * encoding. *

* For more information, see: * JGuru FAQ. *

* Incidentally, this is almost the same as encodeName(), below. * I am not yet entirely sure if it's safe to merge the code. * * @since 1.5.3 * @deprecated JSPWiki now requires servlet API 2.3, which has a better * way of dealing with this stuff. This will be removed in * the near future. */ public String safeGetParameter( ServletRequest request, String name ) { try { String res = request.getParameter( name ); if( res != null ) { res = new String(res.getBytes("ISO-8859-1"), getContentEncoding() ); } return res; } catch( UnsupportedEncodingException e ) { log.fatal( "Unsupported encoding", e ); return ""; } } /** * Returns the query string (the portion after the question mark). * * @return The query string. If the query string is null, * returns an empty string. * * @since 2.1.3 */ public String safeGetQueryString( HttpServletRequest request ) { if (request == null) { return ""; } try { String res = request.getQueryString(); if( res != null ) { res = new String(res.getBytes("ISO-8859-1"), getContentEncoding() ); // // Ensure that the 'page=xyz' attribute is removed // FIXME: Is it really the mandate of this routine to // do that? // int pos1 = res.indexOf("page="); if (pos1 >= 0) { String tmpRes = res.substring(0, pos1); int pos2 = res.indexOf("&",pos1) + 1; if ( (pos2 > 0) && (pos2 < res.length()) ) { tmpRes = tmpRes + res.substring(pos2); } res = tmpRes; } } return res; } catch( UnsupportedEncodingException e ) { log.fatal( "Unsupported encoding", e ); return ""; } } /** * Returns an URL to some other Wiki that we know. * * @return null, if no such reference was found. */ public String getInterWikiURL( String wikiName ) { return TextUtil.getStringProperty(m_properties,PROP_INTERWIKIREF+wikiName,null); } /** * Returns a collection of all supported InterWiki links. */ public Collection getAllInterWikiLinks() { Vector v = new Vector(); for( Enumeration i = m_properties.propertyNames(); i.hasMoreElements(); ) { String prop = (String) i.nextElement(); if( prop.startsWith( PROP_INTERWIKIREF ) ) { v.add( prop.substring( prop.lastIndexOf(".")+1 ) ); } } return v; } /** * Returns a collection of all image types that get inlined. */ public Collection getAllInlinedImagePatterns() { return JSPWikiMarkupParser.getImagePatterns( this ); } /** * If the page is a special page, then returns a direct URL * to that page. Otherwise returns null. *

* Special pages are non-existant references to other pages. * For example, you could define a special page reference * "RecentChanges" which would always be redirected to "RecentChanges.jsp" * instead of trying to find a Wiki page called "RecentChanges". */ public String getSpecialPageReference( String original ) { String propname = PROP_SPECIALPAGE+original; String specialpage = TextUtil.getStringProperty( m_properties, propname, null ); if( specialpage != null ) specialpage = getURL( WikiContext.NONE, specialpage, null, true ); return specialpage; } /** * Returns the name of the application. */ // FIXME: Should use servlet context as a default instead of a constant. public String getApplicationName() { String appName = TextUtil.getStringProperty(m_properties,PROP_APPNAME,Release.APPNAME); return appName; } /** * Beautifies the title of the page by appending spaces in suitable * places, if the user has so decreed in the properties when constructing * this WikiEngine. However, attachment names are not beautified, no * matter what. * * @since 1.7.11 */ public String beautifyTitle( String title ) { if( m_beautifyTitle ) { try { if(m_attachmentManager.getAttachmentInfo(title) == null) { return TextUtil.beautifyString( title ); } } catch( ProviderException e ) { return title; } } return title; } /** * Beautifies the title of the page by appending non-breaking spaces * in suitable places. This is really suitable only for HTML output, * as it uses the &nbsp; -character. * * @since 2.1.127 */ public String beautifyTitleNoBreak( String title ) { if( m_beautifyTitle ) { return TextUtil.beautifyString( title, " " ); } return title; } /** * Returns true, if the requested page (or an alias) exists. Will consider * any version as existing. Will also consider attachments. * * @param page WikiName of the page. */ public boolean pageExists( String page ) { Attachment att = null; try { if( getSpecialPageReference(page) != null ) return true; if( getFinalPageName( page ) != null ) { return true; } att = getAttachmentManager().getAttachmentInfo( (WikiContext)null, page ); } catch( ProviderException e ) { log.debug("pageExists() failed to find attachments",e); } return att != null; } /** * Returns true, if the requested page (or an alias) exists with the * requested version. * * @param page Page name */ public boolean pageExists( String page, int version ) throws ProviderException { if( getSpecialPageReference(page) != null ) return true; String finalName = getFinalPageName( page ); boolean isThere = false; if( finalName != null ) { // // Go and check if this particular version of this page // exists. // isThere = m_pageManager.pageExists( finalName, version ); } if( isThere == false ) { // // Go check if such an attachment exists. // try { isThere = getAttachmentManager().getAttachmentInfo( (WikiContext)null, page, version ) != null; } catch( ProviderException e ) { log.debug("pageExists() failed to find attachments",e); } } return isThere; } /** * Returns true, if the requested page (or an alias) exists, with the * specified version in the WikiPage. * * @since 2.0 */ public boolean pageExists( WikiPage page ) throws ProviderException { if( page != null ) { return pageExists( page.getName(), page.getVersion() ); } return false; } /** * Returns the correct page name, or null, if no such * page can be found. Aliases are considered. *

* In some cases, page names can refer to other pages. For example, * when you have matchEnglishPlurals set, then a page name "Foobars" * will be transformed into "Foobar", should a page "Foobars" not exist, * but the page "Foobar" would. This method gives you the correct * page name to refer to. *

* This facility can also be used to rewrite any page name, for example, * by using aliases. It can also be used to check the existence of any * page. * * @since 2.0 * @param page Page name. * @return The rewritten page name, or null, if the page does not exist. */ public String getFinalPageName( String page ) throws ProviderException { boolean isThere = simplePageExists( page ); if( !isThere && m_matchEnglishPlurals ) { if( page.endsWith("s") ) { page = page.substring( 0, page.length()-1 ); } else { page += "s"; } isThere = simplePageExists( page ); } return isThere ? page : null ; } /** * Just queries the existing pages directly from the page manager. * We also check overridden pages from jspwiki.properties */ private boolean simplePageExists( String page ) throws ProviderException { if( getSpecialPageReference(page) != null ) return true; return m_pageManager.pageExists( page ); } /** * Turns a WikiName into something that can be * called through using an URL. * * @since 1.4.1 */ public String encodeName( String pagename ) { return TextUtil.urlEncode( pagename, (m_useUTF8 ? "UTF-8" : "ISO-8859-1")); } public String decodeName( String pagerequest ) { try { return TextUtil.urlDecode( pagerequest, (m_useUTF8 ? "UTF-8" : "ISO-8859-1") ); } catch( UnsupportedEncodingException e ) { throw new InternalWikiException("ISO-8859-1 not a supported encoding!?! Your platform is borked."); } } /** * Returns the IANA name of the character set encoding we're * supposed to be using right now. * * @since 1.5.3 */ public String getContentEncoding() { if( m_useUTF8 ) return "UTF-8"; return "ISO-8859-1"; } /** * Returns the un-HTMLized text of the latest version of a page. * This method also replaces the < and & -characters with * their respective HTML entities, thus making it suitable * for inclusion on an HTML page. If you want to have the * page text without any conversions, use getPureText(). * * @param page WikiName of the page to fetch. * @return WikiText. */ public String getText( String page ) { return getText( page, WikiPageProvider.LATEST_VERSION ); } /** * Returns the un-HTMLized text of the given version of a page. * This method also replaces the < and & -characters with * their respective HTML entities, thus making it suitable * for inclusion on an HTML page. If you want to have the * page text without any conversions, use getPureText(). * * * @param page WikiName of the page to fetch * @param version Version of the page to fetch * @return WikiText. */ public String getText( String page, int version ) { String result = getPureText( page, version ); // // Replace ampersand first, or else all quotes and stuff // get replaced as well with " etc. // /* result = TextUtil.replaceString( result, "&", "&" ); */ result = TextUtil.replaceEntities( result ); return result; } /** * Returns the un-HTMLized text of the given version of a page in * the given context. USE THIS METHOD if you don't know what * doing. *

* This method also replaces the < and & -characters with * their respective HTML entities, thus making it suitable * for inclusion on an HTML page. If you want to have the * page text without any conversions, use getPureText(). * * @since 1.9.15. */ public String getText( WikiContext context, WikiPage page ) { return getText( page.getName(), page.getVersion() ); } /** * Returns the pure text of a page, no conversions. Use this * if you are writing something that depends on the parsing * of the page. Note that you should always check for page * existence through pageExists() before attempting to fetch * the page contents. * * @param page The name of the page to fetch. * @param version If WikiPageProvider.LATEST_VERSION, then uses the * latest version. * @return The page contents. If the page does not exist, * returns an empty string. */ // FIXME: Should throw an exception on unknown page/version? public String getPureText( String page, int version ) { String result = null; try { result = m_pageManager.getPageText( page, version ); } catch( ProviderException e ) { // FIXME } finally { if( result == null ) result = ""; } return result; } /** * Returns the pure text of a page, no conversions. Use this * if you are writing something that depends on the parsing * the page. Note that you should always check for page * existence through pageExists() before attempting to fetch * the page contents. * * @param page A handle to the WikiPage * @return String of WikiText. * @since 2.1.13. */ public String getPureText( WikiPage page ) { return getPureText( page.getName(), page.getVersion() ); } /** * Returns the converted HTML of the page using a different * context than the default context. */ public String getHTML( WikiContext context, WikiPage page ) { String pagedata = null; pagedata = getPureText( page.getName(), page.getVersion() ); String res = textToHTML( context, pagedata ); return res; } /** * Returns the converted HTML of the page. * * @param page WikiName of the page to convert. */ public String getHTML( String page ) { return getHTML( page, WikiPageProvider.LATEST_VERSION ); } /** * Returns the converted HTML of the page's specific version. * The version must be a positive integer, otherwise the current * version is returned. * * @param pagename WikiName of the page to convert. * @param version Version number to fetch */ public String getHTML( String pagename, int version ) { WikiPage page = getPage( pagename, version ); WikiContext context = new WikiContext( this, page ); context.setRequestContext( WikiContext.NONE ); String res = getHTML( context, page ); return res; } /** * Converts raw page data to HTML. * * @param pagedata Raw page data to convert to HTML */ public String textToHTML( WikiContext context, String pagedata ) { String result = ""; boolean runFilters = "true".equals(m_variableManager.getValue(context,PROP_RUNFILTERS,"true")); StopWatch sw = new StopWatch(); sw.start(); try { if( runFilters ) pagedata = m_filterManager.doPreTranslateFiltering( context, pagedata ); result = m_renderingManager.getHTML( context, pagedata ); if( runFilters ) result = m_filterManager.doPostTranslateFiltering( context, result ); } catch( FilterException e ) { // FIXME: Don't yet know what to do } sw.stop(); if( log.isDebugEnabled() ) log.debug("Page "+context.getRealPage().getName()+" rendered, took "+sw ); return( result ); } /** * Reads a WikiPageful of data from a String and returns all links * internal to this Wiki in a Collection. */ protected Collection scanWikiLinks( WikiPage page, String pagedata ) { LinkCollector localCollector = new LinkCollector(); textToHTML( new WikiContext(this,page), pagedata, localCollector, null, localCollector, false ); return localCollector.getLinks(); } /** * Just convert WikiText to HTML. */ public String textToHTML( WikiContext context, String pagedata, StringTransmutator localLinkHook, StringTransmutator extLinkHook ) { return textToHTML( context, pagedata, localLinkHook, extLinkHook, null, true ); } /** * Just convert WikiText to HTML. */ public String textToHTML( WikiContext context, String pagedata, StringTransmutator localLinkHook, StringTransmutator extLinkHook, StringTransmutator attLinkHook ) { return textToHTML( context, pagedata, localLinkHook, extLinkHook, attLinkHook, true ); } /** * Helper method for doing the HTML translation. */ private String textToHTML( WikiContext context, String pagedata, StringTransmutator localLinkHook, StringTransmutator extLinkHook, StringTransmutator attLinkHook, boolean parseAccessRules ) { String result = ""; if( pagedata == null ) { log.error("NULL pagedata to textToHTML()"); return null; } boolean runFilters = "true".equals(m_variableManager.getValue(context,PROP_RUNFILTERS,"true")); try { StopWatch sw = new StopWatch(); sw.start(); if( runFilters ) pagedata = m_filterManager.doPreTranslateFiltering( context, pagedata ); MarkupParser mp = m_renderingManager.getParser( context, pagedata ); mp.addLocalLinkHook( localLinkHook ); mp.addExternalLinkHook( extLinkHook ); mp.addAttachmentLinkHook( attLinkHook ); if( !parseAccessRules ) mp.disableAccessRules(); result = m_renderingManager.getHTML( context, mp.parse() ); if( runFilters ) result = m_filterManager.doPostTranslateFiltering( context, result ); sw.stop(); if( log.isDebugEnabled() ) log.debug("Page "+context.getRealPage().getName()+" rendered, took "+sw ); } catch( IOException e ) { log.error("Failed to scan page data: ", e); } catch( FilterException e ) { // FIXME: Don't yet know what to do } return result; } /** * Updates all references for the given page. */ public void updateReferences( WikiPage page ) { String pageData = getPureText( page.getName(), WikiProvider.LATEST_VERSION ); m_referenceManager.updateReferences( page.getName(), scanWikiLinks( page, pageData ) ); } /** * Writes the WikiText of a page into the * page repository. * * @since 2.1.28 * @param context The current WikiContext * @param text The Wiki markup for the page. */ public void saveText( WikiContext context, String text ) throws WikiException { WikiPage page = context.getPage(); if( page.getAuthor() == null ) { Principal wup = context.getCurrentUser(); if( wup != null ) page.setAuthor( wup.getName() ); } text = TextUtil.normalizePostData(text); text = m_filterManager.doPreSaveFiltering( context, text ); // Hook into cross reference collection. m_pageManager.putPageText( page, text ); // ARJ HACK: reload the page so we parse ACLs, among other things /* page = getPage( page.getName() ); context.setPage( page ); textToHTML( context, text ); */ m_filterManager.doPostSaveFiltering( context, text ); } /** * Returns the number of pages in this Wiki */ public int getPageCount() { return m_pageManager.getTotalPageCount(); } /** * Returns the provider name */ public String getCurrentProvider() { return m_pageManager.getProvider().getClass().getName(); } /** * return information about current provider. * @since 1.6.4 */ public String getCurrentProviderInfo() { return m_pageManager.getProviderDescription(); } /** * Returns a Collection of WikiPages, sorted in time * order of last change. */ // FIXME: Should really get a Date object and do proper comparisons. // This is terribly wasteful. public Collection getRecentChanges() { try { Collection pages = m_pageManager.getAllPages(); Collection atts = m_attachmentManager.getAllAttachments(); TreeSet sortedPages = new TreeSet( new PageTimeComparator() ); sortedPages.addAll( pages ); sortedPages.addAll( atts ); return sortedPages; } catch( ProviderException e ) { log.error( "Unable to fetch all pages: ",e); return null; } } /** * Parses an incoming search request, then * does a search. *

* The query is dependent on the actual chosen search provider - each one of them has * a language of its own. */ // // FIXME: Should also have attributes attached. // public Collection findPages( String query ) throws ProviderException, IOException { Collection results = m_searchManager.findPages( query ); return results; } /** * Return a bunch of information from the web page. */ public WikiPage getPage( String pagereq ) { return getPage( pagereq, WikiProvider.LATEST_VERSION ); } /** * Returns specific information about a Wiki page. * @since 1.6.7. */ public WikiPage getPage( String pagereq, int version ) { try { WikiPage p = m_pageManager.getPageInfo( pagereq, version ); if( p == null ) { p = m_attachmentManager.getAttachmentInfo( (WikiContext)null, pagereq ); } return p; } catch( ProviderException e ) { log.error( "Unable to fetch page info",e); return null; } } /** * Returns a Collection of WikiPages containing the * version history of a page. */ public List getVersionHistory( String page ) { List c = null; try { c = m_pageManager.getVersionHistory( page ); if( c == null ) { c = m_attachmentManager.getVersionHistory( page ); } } catch( ProviderException e ) { log.error("FIXME"); } return c; } /** * Returns a diff of two versions of a page. * * @param page Page to return * @param version1 Version number of the old page. If * WikiPageProvider.LATEST_VERSION (-1), then uses current page. * @param version2 Version number of the new page. If * WikiPageProvider.LATEST_VERSION (-1), then uses current page. * * @return A HTML-ized difference between two pages. If there is no difference, * returns an empty string. */ public String getDiff( String page, int version1, int version2 ) { String page1 = getPureText( page, version1 ); String page2 = getPureText( page, version2 ); // Kludge to make diffs for new pages to work this way. if( version1 == WikiPageProvider.LATEST_VERSION ) { page1 = ""; } String diff = m_differenceManager.makeDiff( page1, page2 ); return diff; } /** * Returns this object's ReferenceManager. * @since 1.6.1 */ // (FIXME: We may want to protect this, though...) public ReferenceManager getReferenceManager() { return m_referenceManager; } /** * Returns the rendering manager for this wiki application. * * @since 2.3.27 * @return A RenderingManager object. */ public RenderingManager getRenderingManager() { return m_renderingManager; } /** * Returns the current plugin manager. * @since 1.6.1 */ public PluginManager getPluginManager() { return m_pluginManager; } public VariableManager getVariableManager() { return m_variableManager; } /** * Shortcut to getVariableManager().getValue(). However, this method does not * throw a NoSuchVariableException, but returns null in case the variable does * not exist. * * @since 2.2 */ public String getVariable( WikiContext context, String name ) { try { return m_variableManager.getValue( context, name ); } catch( NoSuchVariableException e ) { return null; } } /** * Returns the current PageManager. */ public PageManager getPageManager() { return m_pageManager; } /** * Returns the current AttachmentManager. * @since 1.9.31. */ public AttachmentManager getAttachmentManager() { return m_attachmentManager; } /** * Returns the currently used authorization manager. */ public AuthorizationManager getAuthorizationManager() { return m_authorizationManager; } /** * Returns the currently used authentication manager. */ public AuthenticationManager getAuthenticationManager() { return m_authenticationManager; } /** * Returns the manager responsible for the filters. * @since 2.1.88 */ public FilterManager getFilterManager() { return m_filterManager; } /** * Returns the manager responsible for searching the Wiki. * @since 2.2.21 */ public SearchManager getSearchManager() { return m_searchManager; } /** * Parses the given path and attempts to match it against the list * of specialpages to see if this path exists. It is used to map things * like "UserPreferences.jsp" to page "User Preferences". * * @return WikiName, or null if a match could not be found. */ private String matchSpecialPagePath( String path ) { // // Remove servlet root marker. // if( path.startsWith("/") ) { path = path.substring(1); } for( Iterator i = m_properties.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry) i.next(); String key = (String)entry.getKey(); if( key.startsWith( PROP_SPECIALPAGE ) ) { String value = (String)entry.getValue(); if( value.equals( path ) ) { return key.substring( PROP_SPECIALPAGE.length() ); } } } return null; } /** * Figure out to which page we are really going to. Considers * special page names from the jspwiki.properties, and possible aliases. * * @param context The Wiki Context in which the request is being made. * @return A complete URL to the new page to redirect to * @since 2.2 */ public String getRedirectURL( WikiContext context ) { String pagename = context.getPage().getName(); String redirURL = null; redirURL = getSpecialPageReference( pagename ); if( redirURL == null ) { String alias = (String)context.getPage().getAttribute( WikiPage.ALIAS ); if( alias != null ) { redirURL = getViewURL( alias ); } else { redirURL = (String)context.getPage().getAttribute( WikiPage.REDIRECT ); } } return redirURL; } /** * Shortcut to create a WikiContext from the Wiki page. * * @since 2.1.15. */ // FIXME: We need to have a version which takes a fixed page // name as well, or check it elsewhere. public WikiContext createContext( HttpServletRequest request, String requestContext ) { String pagereq; if( !m_isConfigured ) { throw new InternalWikiException("WikiEngine has not been properly started. It is likely that the configuration is faulty. Please check all logs for the possible reason."); } // // First, set the encoding for parameter parsing. // try { request.setCharacterEncoding( getContentEncoding() ); } catch( UnsupportedEncodingException e ) { log.fatal("Someone gave us a encoding we cannot understand!"); throw new InternalWikiException("Unknown encoding "+getContentEncoding()); } try { pagereq = m_urlConstructor.parsePage( requestContext, request, getContentEncoding() ); } catch( IOException e ) { log.error("Unable to create context",e); throw new InternalWikiException("Big internal booboo, please check logs."); } String template = request.getParameter( "skin" ); // // Figure out the page name. // We also check the list of special pages, which incidentally // allows us to localize them, too. // if( pagereq == null || pagereq.length() == 0 ) { String servlet = request.getServletPath(); log.debug("Servlet path is: "+servlet); pagereq = matchSpecialPagePath( servlet ); log.debug("Mapped to "+pagereq); if( pagereq == null ) { pagereq = getFrontPage(); } } int hashMark = pagereq.indexOf('#'); if( hashMark != -1 ) { pagereq = pagereq.substring( 0, hashMark ); } int version = WikiProvider.LATEST_VERSION; String rev = request.getParameter("version"); if( rev != null ) { version = Integer.parseInt( rev ); } // // Find the WikiPage object // String pagename = pagereq; WikiPage wikipage; try { pagename = getFinalPageName( pagereq ); } catch( ProviderException e ) {} // FIXME: Should not ignore! if( pagename != null ) { wikipage = getPage( pagename, version ); } else { wikipage = getPage( pagereq, version ); } if( wikipage == null ) { pagereq = MarkupParser.cleanLink( pagereq ); wikipage = new WikiPage( this, pagereq ); } // // Figure out which template we should be using for this page. // if( template == null ) { template = (String)wikipage.getAttribute( PROP_TEMPLATEDIR ); // FIXME: Most definitely this should be checked for // existence, or else it is possible to create pages that // cannot be shown. if( template == null || template.length() == 0 ) { template = getTemplateDir(); } } WikiContext context = new WikiContext( this, request, wikipage ); context.setRequestContext( requestContext ); context.setTemplate( template ); return context; } /** * Deletes a page or an attachment completely, including all versions. * * @param pageName * @throws ProviderException */ public void deletePage( String pageName ) throws ProviderException { WikiPage p = getPage( pageName ); if( p instanceof Attachment ) { m_attachmentManager.deleteAttachment( (Attachment) p ); } else { if (m_attachmentManager.hasAttachments( p )) { Collection attachments = m_attachmentManager.listAttachments( p ); for( Iterator atti = attachments.iterator(); atti.hasNext(); ) { m_attachmentManager.deleteAttachment( (Attachment)(atti.next()) ); } } m_pageManager.deletePage( p ); } } /** * Deletes a specific version of a page or an attachment. * * @param page * @throws ProviderException */ public void deleteVersion( WikiPage page ) throws ProviderException { if( page instanceof Attachment ) { m_attachmentManager.deleteVersion( (Attachment) page ); } else { m_pageManager.deleteVersion( page ); } } /** * Returns the URL of the global RSS file. May be null, if the * RSS file generation is not operational. * @since 1.7.10 */ public String getGlobalRSSURL() { if( m_rssURL != null ) { return getBaseURL()+m_rssURL; } return null; } /** * @since 2.2 */ public String getRootPath() { return m_rootPath; } /** * @since 2.2.6 * @return the URL constructor */ public URLConstructor getURLConstructor() { return m_urlConstructor; } /** * @since 2.1.165 * @return the RSS generator */ public RSSGenerator getRSSGenerator() { return m_rssGenerator; } /** * Runs the RSS generation thread. * FIXME: MUST be somewhere else, this is not a good place. */ private class RSSThread extends Thread { public void run() { setName("JSPWiki Global RSS generator thread"); try { String fileName = TextUtil.getStringProperty( m_properties, RSSGenerator.PROP_RSSFILE, "rss.rdf" ); int rssInterval = TextUtil.getIntegerProperty( m_properties, RSSGenerator.PROP_INTERVAL, 3600 ); log.debug("RSS file will be at "+fileName); log.debug("RSS refresh interval (seconds): "+rssInterval); while(true) { Writer out = null; Reader in = null; try { // // Generate RSS file, output it to // default "rss.rdf". // log.debug("Regenerating RSS feed to "+fileName); String feed = m_rssGenerator.generate(); File file = new File( m_rootPath, fileName ); in = new StringReader(feed); out = new BufferedWriter( new OutputStreamWriter( new FileOutputStream(file), "UTF-8") ); FileUtil.copyContents( in, out ); m_rssURL = fileName; } catch( IOException e ) { log.error("Cannot generate RSS feed to "+fileName, e ); m_rssURL = null; } finally { try { if( in != null ) in.close(); if( out != null ) out.close(); } catch( IOException e ) { log.fatal("Could not close I/O for RSS", e ); break; } } Thread.sleep(rssInterval*1000L); } // while } catch(InterruptedException e) { log.error("RSS thread interrupted, no more RSS feeds", e); } // // Signal: no more RSS feeds. // m_rssURL = null; } } /** * Renames, or moves, a wiki page. Can also alter referring wiki * links to point to the renamed page. * * @param renameFrom Name of the source page. * @param renameTo Name of the destination page. * @param changeReferrers If true, then changes any referring links * to point to the renamed page. * * @return The name of the page that the source was renamed to. * * @throws WikiException In the case of an error, such as the destination * page already existing. */ public String renamePage( String renameFrom, String renameTo, boolean changeReferrers) throws WikiException { return m_pageRenamer.renamePage(renameFrom, renameTo, changeReferrers); } /** * Returns the UserDatabase employed by this WikiEngine. * The UserDatabase is lazily initialized. * @since 2.3 */ /** * Returns the UserManager employed by this WikiEngine. * @since 2.3 */ public UserManager getUserManager() { return m_userManager; } // FIXME: Must not throw RuntimeException, but something else. public UserDatabase getUserDatabase() { return m_userManager.getUserDatabase(); } /** * Returns the GroupManager employed by this WikiEngine. * The GroupManager is lazily initialized. * @since 2.3 */ public GroupManager getGroupManager() { return m_userManager.getGroupManager(); } /** * Returns the AclManager employed by this WikiEngine. * The AclManager is lazily initialized. * @since 2.3 */ public AclManager getAclManager() { if (m_aclManager == null) { // TODO: make this pluginizable m_aclManager = new DefaultAclManager(); m_aclManager.initialize( this, m_properties ); } return m_aclManager; } public EditorManager getEditorManager() { return m_editorManager; } }