You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC

svn commit: r627255 [36/41] - in /incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/ com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/ com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/EditorManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/EditorManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/EditorManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/EditorManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,338 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 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.ui;
+
+import java.net.URL;
+import java.util.*;
+
+import javax.servlet.jsp.PageContext;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.jdom.xpath.XPath;
+
+import com.ecyrd.jspwiki.NoSuchVariableException;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.action.PreviewActionBean;
+import com.ecyrd.jspwiki.modules.ModuleManager;
+import com.ecyrd.jspwiki.modules.WikiModuleInfo;
+import com.ecyrd.jspwiki.plugin.PluginManager;
+import com.ecyrd.jspwiki.preferences.Preferences;
+
+/**
+ *  Defines an editor manager.  An editor can be added by adding a
+ *  suitable JSP file under templates/default/editors
+ *  If you want your editor to include any scripts or something, you
+ *  can simply request it by adding the following in your
+ *  ini/jspwiki_module.xml:
+ *
+ *  <pre>
+ *  <modules>
+ *   <editor name="myeditor">
+ *       <author>Janne Jalkanen</author>
+ *       <script>foo.js</script>
+ *       <stylesheet>foo.css</stylesheet>
+ *       <path>editors/myeditor.jsp</path>
+ *   </editor>
+ *  </modules>
+ *  </pre>
+ *
+ *  @author jalkanen
+ *  @author Christoph Sauer
+ *  @author Chuck Smith
+ *  @author Dirk Frederickx
+ *  @since 2.4
+ */
+public class EditorManager extends ModuleManager
+{
+    /** The property name for setting the editor.  Current value is "jspwiki.editor" */
+    /* not used anymore -- replaced by defaultpref.template.editor */
+    public static final String       PROP_EDITORTYPE = "jspwiki.editor";
+
+    /** Parameter for changing editors at run-time */
+    public static final String       PARA_EDITOR     = "editor";
+
+    /** Known name for the plain wikimarkup editor. */
+    public static final String       EDITOR_PLAIN    = "plain";
+
+    /** Known name for the preview editor component. */
+    public static final String       EDITOR_PREVIEW  = "preview";
+
+    /** Known attribute name for storing the user edited text inside a HTTP parameter. */
+    public static final String       REQ_EDITEDTEXT  = "_editedtext";
+
+    /** Known attribute name for storing the user edited text inside a session or a page context */
+    public static final String       ATTR_EDITEDTEXT = REQ_EDITEDTEXT;
+
+    private             Map          m_editors;
+
+    private static      Logger       log             = Logger.getLogger( EditorManager.class );
+
+    public EditorManager( WikiEngine engine )
+    {
+        super(engine);
+    }
+
+    /**
+     *  Initializes the EditorManager.  It also registers any editors it can find.
+     *
+     *  @param props  Properties for setup.
+     */
+    public void initialize( Properties props )
+    {
+        registerEditors();
+    }
+
+    /**
+     *  This method goes through the jspwiki_module.xml files and hunts for editors.
+     *  Any editors found are put in the registry.
+     *
+     */
+    private void registerEditors()
+    {
+        log.info( "Registering editor modules" );
+
+        m_editors = new HashMap();
+        SAXBuilder builder = new SAXBuilder();
+
+        try
+        {
+            //
+            // Register all editors which have created a resource containing its properties.
+            //
+            // Get all resources of all modules
+            //
+
+            Enumeration resources = getClass().getClassLoader().getResources( PLUGIN_RESOURCE_LOCATION );
+
+            while( resources.hasMoreElements() )
+            {
+                URL resource = (URL) resources.nextElement();
+
+                try
+                {
+                    log.debug( "Processing XML: " + resource );
+
+                    Document doc = builder.build( resource );
+
+                    List plugins = XPath.selectNodes( doc, "/modules/editor");
+
+                    for( Iterator i = plugins.iterator(); i.hasNext(); )
+                    {
+                        Element pluginEl = (Element) i.next();
+
+                        String name = pluginEl.getAttributeValue("name");
+
+                        WikiEditorInfo info = WikiEditorInfo.newInstance(name, pluginEl);
+
+                        if( checkCompatibility(info) )
+                        {
+                            m_editors.put( name, info );
+
+                            log.debug("Registered editor "+name);
+                        }
+                        else
+                        {
+                            log.info("Editor '"+name+"' not compatible with this version of JSPWiki.");
+                        }
+                    }
+                }
+                catch( java.io.IOException e )
+                {
+                    log.error( "Couldn't load " + PluginManager.PLUGIN_RESOURCE_LOCATION + " resources: " + resource, e );
+                }
+                catch( JDOMException e )
+                {
+                    log.error( "Error parsing XML for plugin: "+PluginManager.PLUGIN_RESOURCE_LOCATION );
+                }
+            }
+        }
+        catch( java.io.IOException e )
+        {
+            log.error( "Couldn't load all " + PLUGIN_RESOURCE_LOCATION + " resources", e );
+        }
+    }
+
+    /**
+     *  Returns an editor for the current context.  The editor names are matched in
+     *  a case insensitive manner.  At the moment, the only place that this method
+     *  looks in is the property file, but in the future this will also look at
+     *  user preferences.
+     *  <p>
+     *  Determines the editor to use by the following order of conditions:
+     *  1. Editor set in User Preferences
+     *  2. Default Editor set in jspwiki.properties
+     *  <p>
+     *  For the PREVIEW context, this method returns the "preview" editor.
+     *
+     * @param context The context that is chosen.
+     * @return The name of the chosen editor.  If no match could be found, will
+     *         revert to the default "plain" editor.
+     */
+    public String getEditorName( WikiContext context )
+    {
+        if( context instanceof PreviewActionBean )
+            return EDITOR_PREVIEW;
+
+        String editor = null;
+
+        // User has set an editor in preferences
+        editor = Preferences.getPreference( context, PARA_EDITOR );
+
+        /* FIXME: actual default 'editor' property is read by the Preferences class */
+        if (editor == null)
+        {
+            // or use the default editor in jspwiki.properties
+            try
+            {
+                editor = m_engine.getVariableManager().getValue( context, PROP_EDITORTYPE );
+            }
+            catch( NoSuchVariableException e ) {} // This is fine
+        }
+
+        if (editor != null)
+        {
+            String[] editorlist = getEditorList();
+
+            editor = editor.trim();
+
+            for( int i = 0; i < editorlist.length; i++ )
+            {
+                if( editorlist[i].equalsIgnoreCase(editor))
+                {
+                    return editorlist[i];
+                }
+            }
+        }
+
+        return EDITOR_PLAIN;
+    }
+
+    /**
+     *  Returns a list of editors as Strings of editor names.
+     *
+     *  @return the list of available editors
+     */
+    public String[] getEditorList()
+    {
+        String[] editors = new String[m_editors.size()];
+
+        Set keys = m_editors.keySet();
+
+        return (String[]) keys.toArray( editors );
+    }
+
+    /**
+     *  Convenience method for getting the path to the editor JSP file.
+     *
+     *  @param context
+     *  @return e.g. "editors/plain.jsp"
+     */
+    public String getEditorPath( WikiContext context )
+    {
+        String path = null;
+
+        String editor = getEditorName( context );
+
+        WikiEditorInfo ed = (WikiEditorInfo)m_editors.get( editor );
+
+        if( ed != null )
+        {
+            path = ed.getPath();
+        }
+        else
+        {
+            path = "editors/"+editor+".jsp";
+        }
+
+        return path;
+    }
+
+    /**
+     *  Convenience function which examines the current context and attempts to figure
+     *  out whether the edited text is in the HTTP request parameters or somewhere in
+     *  the session.
+     *
+     *  @param ctx the JSP page context
+     *  @return the edited text, if present in the session page context or as a parameter
+     */
+    public static String getEditedText( PageContext ctx )
+    {
+        String usertext = ctx.getRequest().getParameter( REQ_EDITEDTEXT );
+
+        if( usertext == null )
+        {
+            usertext = (String)ctx.findAttribute( ATTR_EDITEDTEXT );
+        }
+
+        return usertext;
+    }
+
+    /**
+     *  Contains info about an editor.
+     *
+     *  @author jalkanen
+     *
+     */
+    private static final class WikiEditorInfo
+        extends WikiModuleInfo
+    {
+        private String m_path;
+
+        protected static WikiEditorInfo newInstance( String name, Element el )
+        {
+            if( name == null || name.length() == 0 ) return null;
+            WikiEditorInfo info = new WikiEditorInfo( name );
+
+            info.initializeFromXML( el );
+            return info;
+        }
+
+        protected void initializeFromXML( Element el )
+        {
+            super.initializeFromXML( el );
+            m_path = el.getChildText("path");
+        }
+
+        private WikiEditorInfo( String name )
+        {
+            super(name);
+        }
+
+        public String getPath()
+        {
+            return m_path;
+        }
+    }
+
+    public Collection modules()
+    {
+        ArrayList ls = new ArrayList();
+
+        ls.addAll( m_editors.values() );
+
+        return ls;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GenericHTTPHandler.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GenericHTTPHandler.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GenericHTTPHandler.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GenericHTTPHandler.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,55 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 JSPWiki development group
+
+    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.ui;
+
+import com.ecyrd.jspwiki.WikiContext;
+
+/**
+ *  Provides a generic HTTP handler interface.
+ *  
+ *  @author jalkanen
+ *
+ */
+public interface GenericHTTPHandler
+{
+    
+    /**
+     *  Get an identifier for this particular AdminBean.  This id MUST
+     *  conform to URI rules.  The ID must also be unique across all HTTPHandlers.
+     *  
+     *  @return
+     */
+    public String getId();
+    
+    /**
+     *  Return basic HTML.
+     *  
+     *  @param context
+     *  @return
+     */
+    public String doGet( WikiContext context );
+    
+    /**
+     *  Handles a POST response.
+     *  @param context
+     *  @return
+     */
+    public String doPost( WikiContext context );
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GroupTypeConverter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GroupTypeConverter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GroupTypeConverter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/GroupTypeConverter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,83 @@
+/* Copyright 2005-2006 Tim Fennell
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ecyrd.jspwiki.ui;
+
+import java.util.Collection;
+import java.util.Locale;
+
+import net.sourceforge.stripes.controller.StripesFilter;
+import net.sourceforge.stripes.validation.LocalizableError;
+import net.sourceforge.stripes.validation.TypeConverter;
+import net.sourceforge.stripes.validation.ValidationError;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.authorize.Group;
+import com.ecyrd.jspwiki.auth.authorize.GroupManager;
+
+/**
+ * Stripes type converter that converts a Group name, expressed as a String,
+ * into an {@link com.ecyrd.jspwiki.auth.authorize.Group} object. This converter
+ * is looked up and returned by {@link WikiTypeConverterFactory} for HTTP
+ * request parameters that need to be bound to ActionBean properties of type
+ * Group. Stripes executes this TypeConverter during the
+ * {@link net.sourceforge.stripes.controller.LifecycleStage#BindingAndValidation}
+ * stage of request processing.
+ * 
+ * @author Andrew Jaquith
+ */
+public class GroupTypeConverter implements TypeConverter<Group>
+{
+    /**
+     * Converts a named wiki group into a valid
+     * {@link com.ecyrd.jspwiki.auth.authorize.Group} object by retrieving it
+     * via the Wiki{@link com.ecyrd.jspwiki.auth.authorize.GroupManager}. If
+     * the group cannot be found (perhaps because it does not exist), this
+     * method will add a validation error to the supplied Collection of errors.
+     * The error will be of type
+     * {@link net.sourceforge.stripes.validation.LocalizableError} and will have
+     * a message key of <code>pageNotFound</code> and a single parameter
+     * (equal to the value passed for <code>groupName</code>).
+     * 
+     * @param groupName
+     *            the name of the WikiPage to retrieve
+     * @param targetType
+     *            the type to return, which will always be of type
+     *            {@link com.ecyrd.jspwiki.auth.authorize.Group}
+     * @param errors
+     *            the current Collection of validation errors for this field
+     * @return the resolved Group
+     */
+    public Group convert(String groupName, Class<? extends Group> targetType, Collection<ValidationError> errors)
+    {
+        WikiRuntimeConfiguration config = (WikiRuntimeConfiguration)StripesFilter.getConfiguration();
+        WikiEngine engine = config.getEngine();
+        GroupManager mgr = engine.getGroupManager();
+        Group group = null;
+        try
+        {
+            group = mgr.getGroup(groupName);
+        }
+        catch (NoSuchPrincipalException e)
+        {
+            errors.add(new LocalizableError("groupNotFound", groupName));
+        }
+        return group;
+    }
+
+    public void setLocale(Locale locale)
+    {
+    };
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/InputValidator.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/InputValidator.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/InputValidator.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/InputValidator.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,188 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2006 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.ui;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.ecyrd.jspwiki.WikiSession;
+import com.ecyrd.jspwiki.i18n.InternationalizationManager;
+
+/**
+ * Provides basic validation services for HTTP parameters. Three standard
+ * validators are provided: email address, identifier and standard input. Standard input
+ * validator will reject any HTML-like input, and any of a number of special
+ * characters.  ID validator rejects HTML and quoted strings, and a couple of special characters.
+ * @author Andrew Jaquith
+ * @since 2.3.54
+ */
+public final class InputValidator
+{
+    /** Standard input validator. */
+    public static final int        STANDARD       = 0;
+
+    /** Input validator for e-mail addresses. **/
+    public static final int        EMAIL          = 1;
+
+    /**
+     * @since 2.4.82
+     */
+    public static final int        ID             = 2;
+
+    protected static final Pattern EMAIL_PATTERN  = Pattern.compile( "^[0-9a-zA-Z-_\\.\\+]+@([0-9a-zA-Z-_]+\\.)+[a-zA-Z]+$" );
+
+    protected static final Pattern UNSAFE_PATTERN = Pattern.compile( "[\\x00\\r\\n\\x0f\"':<>;&@\\xff{}\\$%\\\\]" );
+
+    /** Used when checking against IDs such as a full name when saving groups.
+     *  @since 2.4.82 */
+    protected static final Pattern ID_PATTERN     = Pattern.compile( "[\\x00\\r\\n\\x0f\"'<>;&\\xff{}]" );
+
+    private final String           m_form;
+
+    private final WikiSession      m_session;
+
+    /**
+     * Constructs a new input validator for a specific form and wiki session.
+     * When validation errors are detected, they will be added to the wiki
+     * session's messages.
+     * @param form the ID or name of the form this validator should be
+     * associated with
+     * @param session the wiki session
+     */
+    public InputValidator( String form, WikiSession session )
+    {
+        m_form = form;
+        m_session = session;
+    }
+
+    /**
+     * Validates a string against the {@link #STANDARD} validator and
+     * additionally checks that the value is not <code>null</code> or blank.
+     * @param input the string to validate
+     * @param label the label for the string or field ("E-mail address")
+     * @return returns <code>true</code> if valid, <code>false</code>
+     * otherwise
+     */
+    public final boolean validateNotNull( String input, String label )
+    {
+        return validateNotNull( input, label, STANDARD );
+    }
+
+    /**
+     * Validates a string against a particular pattern type and additionally
+     * checks that the value is not <code>null</code> or blank. Delegates to
+     * {@link #validate(String, String, int)}.
+     * @param input the string to validate
+     * @param label the label for the string or field ("E-mail address")
+     * @param type the pattern type to use (<em>e.g.</em>, {@link #STANDARD, #EMAIL}.
+     * @return returns <code>true</code> if valid, <code>false</code>
+     * otherwise
+     */
+    public final boolean validateNotNull( String input, String label, int type )
+    {
+        if ( isBlank( input ) )
+        {
+            ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE,
+                                                          m_session.getLocale() );
+            
+            Object[] args = { label };
+            m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.cantbenull"),
+                                                                args ) );
+            return false;
+        }
+        return validate( input, label, type ) && !isBlank( input );
+    }
+
+    /**
+     * Validates a string against a particular pattern type: e-mail address,
+     * standard HTML input, etc. Note that a blank or null string will
+     * always validate.
+     * @param input the string to validate
+     * @param label the label for the string or field ("E-mail address")
+     * @param type the target pattern to validate against ({@link #STANDARD},
+     * {@link #EMAIL})
+     * @return returns <code>true</code> if valid, <code>false</code>
+     * otherwise
+     */
+    public final boolean validate( String input, String label, int type )
+    {
+        // If blank, it's valid
+        if ( isBlank( input ) )
+        {
+            return true;
+        }
+
+        ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE,
+                                                      m_session.getLocale() );
+
+        // Otherwise, see if it matches the pattern for the target type
+        Matcher matcher;
+        boolean valid;
+        switch( type )
+        {
+        case STANDARD:
+            matcher = UNSAFE_PATTERN.matcher( input );
+            valid = !matcher.find();
+            if ( !valid )
+            {
+                Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;\\@{}%$" };
+                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"),
+                                                                    args ) );
+            }
+            return valid;
+        case EMAIL:
+            matcher = EMAIL_PATTERN.matcher( input );
+            valid = matcher.matches();
+            if ( !valid )
+            {
+                Object[] args = { label };
+                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.invalidemail"),
+                                                                    args ) );
+            }
+            return valid;
+        case ID:
+            matcher = ID_PATTERN.matcher( input );
+            valid = !matcher.find();
+            if ( !valid )
+            {
+                Object[] args = { label, "&quot;&#39;&lt;&gt;;&amp;{}" };
+                m_session.addMessage( m_form, MessageFormat.format( rb.getString("validate.unsafechars"),
+                                                                    args ) );
+            }
+            return valid;
+         default:
+             break;
+        }
+        throw new IllegalArgumentException( "Invalid input type." );
+    }
+
+    /**
+     * Returns <code>true</code> if a supplied string is null or blank
+     * @param input the string to check
+     * @return <code>true</code> if <code>null</code> or blank (zero-length);
+     * <code>false</code> otherwise
+     */
+    public static final boolean isBlank( String input )
+    {
+        return input == null || input.trim().length() < 1;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/Installer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/Installer.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/Installer.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/Installer.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,361 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 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.ui;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.auth.AuthenticationManager;
+import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
+import com.ecyrd.jspwiki.auth.UserManager;
+import com.ecyrd.jspwiki.auth.WikiPrincipal;
+import com.ecyrd.jspwiki.auth.WikiSecurityException;
+import com.ecyrd.jspwiki.auth.authorize.Group;
+import com.ecyrd.jspwiki.auth.authorize.GroupManager;
+import com.ecyrd.jspwiki.auth.user.UserDatabase;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+import com.ecyrd.jspwiki.providers.BasicAttachmentProvider;
+import com.ecyrd.jspwiki.providers.FileSystemProvider;
+import com.ecyrd.jspwiki.util.CommentedProperties;
+
+/**
+ * Manages JSPWiki installation on behalf of <code>admin/Install.jsp</code>.
+ * The contents of this class were previously part of <code>Install.jsp</code>.
+ * @author Janne Jalkanen
+ * @since 2.4.20
+ */
+public class Installer
+{
+    public static final String ADMIN_ID = "admin";
+    public static final String ADMIN_NAME = "Administrator";
+    public static final String INSTALL_INFO = "Installer.Info";
+    public static final String INSTALL_WARNING = "Installer.Warning";
+    public static final String INSTALL_ERROR = "Installer.Error";
+    public static final String APP_NAME = WikiEngine.PROP_APPNAME;
+    public static final String BASE_URL = WikiEngine.PROP_BASEURL;
+    public static final String STORAGE_DIR = BasicAttachmentProvider.PROP_STORAGEDIR;
+    public static final String LOG_DIR = "log4j.appender.FileLog.File";
+    public static final String PAGE_DIR = FileSystemProvider.PROP_PAGEDIR;
+    public static final String WORK_DIR = WikiEngine.PROP_WORKDIR;
+    public static final String ADMIN_GROUP = "Admin";
+    private final WikiSession m_session;
+    private final File m_propertyFile;
+    private final Properties m_props;
+    private final WikiEngine m_engine;
+    private HttpServletRequest m_request;
+    private boolean m_validated;
+    
+    public Installer( HttpServletRequest request, ServletConfig config )
+    {
+        // Get wiki session for this user
+        m_engine = WikiEngine.getInstance( config );
+        m_session = WikiSession.getWikiSession( m_engine, request );
+        
+        // Get the servlet context, and file for properties
+        ServletContext context = config.getServletContext();
+        String path = context.getRealPath("/");
+        m_propertyFile = new File( path, PropertyReader.DEFAULT_PROPERTYFILE );
+        m_props = new CommentedProperties();
+        
+        // Stash the request
+        m_request = request;
+        m_validated = false;
+    }
+    
+    /**
+     * Returns <code>true</code> if the administrative user had
+     * been created previously.
+     * @return the result
+     */
+    public boolean adminExists()
+    {
+        // See if the admin user exists already
+        UserManager userMgr = m_engine.getUserManager();
+        UserDatabase userDb = userMgr.getUserDatabase();
+        
+        try
+        {
+            userDb.findByLoginName( ADMIN_ID );
+            return true;
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            return false;
+        }
+    }
+    
+    /**
+     * Creates an adminstrative user and returns the new password.
+     * If the admin user exists, the password will be <code>null</code>.
+     * @return the password
+     * @throws WikiSecurityException
+     */
+    public String createAdministrator() throws WikiSecurityException
+    {
+        if ( !m_validated )
+        {
+            throw new WikiSecurityException( "Cannot create administrator because one or more of the installation settings are invalid." );
+        }
+        
+        if ( adminExists() )
+        {
+            return null;
+        }
+        
+        // See if the admin user exists already
+        UserManager userMgr = m_engine.getUserManager();
+        UserDatabase userDb = userMgr.getUserDatabase();
+        String password = null;
+        
+        try
+        {
+            userDb.findByLoginName( ADMIN_ID );
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            // Create a random 12-character password
+            password = TextUtil.generateRandomPassword();
+            UserProfile profile = userDb.newProfile();
+            profile.setLoginName( ADMIN_ID );
+            profile.setFullname( ADMIN_NAME );
+            profile.setPassword( password );
+            userDb.save( profile );
+        }
+        
+        // Create a new admin group
+        GroupManager groupMgr = m_engine.getGroupManager();
+        Group group = null;
+        try
+        {
+            group = groupMgr.getGroup( ADMIN_GROUP );
+            group.add( new WikiPrincipal( ADMIN_NAME ) );
+        }
+        catch ( NoSuchPrincipalException e )
+        {
+            group = groupMgr.parseGroup( ADMIN_GROUP, ADMIN_NAME, true );
+        }
+        groupMgr.setGroup( m_session, group );
+        
+        return password;
+    }
+    
+    /**
+     * Returns the properties file as a string
+     * @return the string
+     */
+    public String getProperties()
+    {
+        return m_props.toString();
+    }
+    
+    public String getPropertiesPath()
+    {
+        return m_propertyFile.getAbsolutePath();
+    }
+
+    /**
+     * Returns a property from the WikiEngine's properties.
+     * @param key the property key
+     * @return the property value
+     */
+    public String getProperty( String key )
+    {
+        return m_props.getProperty( key );
+    }
+    
+    public void parseProperties () throws Exception
+    {
+        m_validated = false;
+        
+        // Set request encoding
+        m_request.setCharacterEncoding("UTF-8");
+        
+        try
+        {
+            InputStream in = null; 
+            try
+            {
+                // Load old properties from disk
+                in = new FileInputStream( m_propertyFile ); 
+                m_props.load( in );
+            }
+            finally
+            {
+                if( in != null ) 
+                {
+                    in.close();
+                }
+            }
+        }
+        catch( IOException e )
+        {
+            m_session.addMessage( INSTALL_ERROR, 
+                "Unable to read properties: " +
+                e.getMessage() );
+        }
+        
+        // Get application name
+        String nullValue = m_props.getProperty( APP_NAME, "MyWiki" );
+        parseProperty( APP_NAME, nullValue );
+        
+        // Get/sanitize base URL
+        nullValue = m_request.getRequestURL().toString();
+        nullValue = nullValue.substring( 0, nullValue.lastIndexOf('/') )+"/";
+        nullValue = m_props.getProperty( BASE_URL, nullValue );
+        parseProperty( BASE_URL, nullValue );
+        sanitizeURL( BASE_URL );
+        
+        // Get/sanitize page directory
+        nullValue = m_props.getProperty( PAGE_DIR, "Please configure me!" );
+        parseProperty( PAGE_DIR, nullValue );
+        sanitizePath( PAGE_DIR );
+        
+        // Get/sanitize log directory
+        nullValue = m_props.getProperty( LOG_DIR, "/tmp/" );
+        parseProperty( LOG_DIR, nullValue );
+        sanitizePath( LOG_DIR );
+        
+        // Get/sanitize work directory
+        nullValue = m_props.getProperty( WORK_DIR, "/tmp/" );
+        parseProperty( WORK_DIR, nullValue );
+        sanitizePath( WORK_DIR );
+        
+        // Get/sanitize security property
+        nullValue = m_props.getProperty( AuthenticationManager.PROP_SECURITY, AuthenticationManager.SECURITY_JAAS );
+        parseProperty( AuthenticationManager.PROP_SECURITY, nullValue );
+        
+        // Set a few more default properties, for easy setup
+        m_props.setProperty( STORAGE_DIR, m_props.getProperty( PAGE_DIR ) );
+        m_props.setProperty( PageManager.PROP_PAGEPROVIDER, "VersioningFileProvider" );
+        m_props.setProperty( WikiEngine.PROP_ENCODING, "UTF-8" );
+    }
+    
+    public void saveProperties()
+    {
+        // Write the file back to disk
+        try
+        {
+            OutputStream out = null;
+            try
+            {
+                out = new FileOutputStream( m_propertyFile );
+                m_props.store( out, null );
+            }
+            finally
+            {
+                if ( out != null )
+                {
+                    out.close();
+                }
+            }
+            m_session.addMessage( INSTALL_INFO, 
+                "Your new properties have been saved.  Please restart your container (unless this was your first install).  Scroll down a bit to see your new jspwiki.properties." );
+        }
+        catch( IOException e )
+        {
+            m_session.addMessage( INSTALL_ERROR, 
+                "Unable to write properties: " +
+                e.getMessage() +
+                ". Please copy the file below as your jspwiki.properties:\n" +
+                m_props.toString() );
+        }
+    }
+    
+    public boolean validateProperties() throws Exception
+    {
+        m_session.clearMessages( INSTALL_ERROR );
+        parseProperties();
+        validateNotNull( BASE_URL, "You must define the base URL for this wiki." );
+        validateNotNull( PAGE_DIR, "You must define the location where the files are stored." );
+        validateNotNull( APP_NAME, "You must define the application name." );
+        validateNotNull( WORK_DIR, "You must define a work directory." );
+        validateNotNull( LOG_DIR, "You must define a log directory." );
+        
+        if ( m_session.getMessages( INSTALL_ERROR ).length == 0 )
+        {
+            m_validated = true;
+        }
+        return m_validated;
+    }
+        
+    /**
+     * Sets a property based on the value of an HTTP request parameter.
+     * If the parameter is not found, a default value is used instead.
+     * @param request the HTTP request
+     * @param param the parameter containing the value we will extract
+     * @param defaultValue the default to use if the parameter was not passed
+     * in the request
+     */
+    private void parseProperty( String param, String defaultValue )
+    {
+        String value = m_request.getParameter( param );
+        if ( value == null )
+        {
+            value = defaultValue;
+        }
+        m_props.put( param, value );
+    }
+    
+    /**
+     * Simply sanitizes any path which contains backslashes (sometimes Windows
+     * users may have them) by expanding them to double-backslashes
+     * @param s the key of the property to sanitize
+     */
+    private void sanitizePath( String key )
+    {
+        String s = m_props.getProperty( key );
+        s = TextUtil.replaceString(s, "\\", "\\\\" );
+        s = s.trim();
+        m_props.put( key, s );
+    }
+    
+    /**
+     * Simply sanitizes any URL which contains backslashes (sometimes Windows
+     * users may have them)
+     * @param s the key of the property to sanitize
+     */
+    private void sanitizeURL( String key )
+    {
+        String s = m_props.getProperty( key );
+        s = TextUtil.replaceString( s, "\\", "/" );
+        s = s.trim();
+        m_props.put( key, s );
+    }
+
+    private void validateNotNull( String key, String message )
+    {
+        String value = m_props.getProperty( key );
+        if ( value == null || value.length() == 0 )
+        {
+            m_session.addMessage( INSTALL_ERROR, message );
+        }
+    }
+    
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/PrincipalTypeConverter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/PrincipalTypeConverter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/PrincipalTypeConverter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/PrincipalTypeConverter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,62 @@
+/* Copyright 2005-2006 Tim Fennell
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ecyrd.jspwiki.ui;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Locale;
+
+import net.sourceforge.stripes.validation.TypeConverter;
+import net.sourceforge.stripes.validation.ValidationError;
+
+import com.ecyrd.jspwiki.auth.WikiPrincipal;
+
+/**
+ * Stripes type converter that converts a Principal name, expressed as a String, into an
+ * {@link com.ecyrd.jspwiki.auth.WikiPrincipal} object. This converter is looked up
+ * and returned by {@link WikiTypeConverterFactory} for HTTP request parameters
+ * that need to be bound to ActionBean properties of type Principal. Stripes
+ * executes this TypeConverter during the
+ * {@link net.sourceforge.stripes.controller.LifecycleStage#BindingAndValidation}
+ * stage of request processing.
+ * 
+ * @author Andrew Jaquith
+ */
+public class PrincipalTypeConverter implements TypeConverter<Principal>
+{
+
+    /**
+     * Converts a named user, passed as a String, into a valid
+     * {@link com.ecyrd.jspwiki.auth.WikiPrincipal} object. This method will not
+     * ever return errors.
+     * 
+     * @param principalName
+     *            the name of the Principal to create
+     * @param targetType
+     *            the type to return, which will always be of type
+     *            {@link java.security.Principal}
+     * @param errors
+     *            the current Collection of validation errors for this field
+     * @return the
+     */
+    public Principal convert(String principalName, Class<? extends Principal> targetType, Collection<ValidationError> errors)
+    {
+        return new WikiPrincipal(principalName);
+    }
+
+    public void setLocale(Locale locale)
+    {
+    };
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/TemplateManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/TemplateManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/TemplateManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/TemplateManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,607 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2003-2006 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.ui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.InternalWikiException;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.action.WikiActionBean;
+import com.ecyrd.jspwiki.modules.ModuleManager;
+
+/**
+ *  This class takes care of managing JSPWiki templates.  This class also provides
+ *  the ResourceRequest mechanism.
+ *
+ *  @since 2.1.62
+ *  @author Janne Jalkanen
+ */
+public class TemplateManager
+    extends ModuleManager
+{
+    private static final String SKIN_DIRECTORY = "skins";
+
+    /**
+     * Requests a JavaScript function to be called during window.onload. Value is {@value}.
+     */
+    public static final String RESOURCE_JSFUNCTION = "jsfunction";
+
+    /**
+     * Requests a JavaScript associative array with all localized strings.
+     */
+    public static final String RESOURCE_JSLOCALIZEDSTRINGS = "jslocalizedstrings";
+
+    /**
+     * Requests a stylesheet to be inserted. Value is {@value}.
+     */
+    public static final String RESOURCE_STYLESHEET = "stylesheet";
+
+    /**
+     * Requests a script to be loaded.  Value is {@value}.
+     */
+    public static final String RESOURCE_SCRIPT     = "script";
+
+    /**
+     *  Requests inlined CSS. Value is {@value}.
+     */
+    public static final String RESOURCE_INLINECSS  = "inlinecss";
+
+    /** The default directory for the properties. Value is {@value}. */
+    public static final String DIRECTORY           = "templates";
+
+    /** The name of the default template.  Value is {@value}. */
+    public static final String DEFAULT_TEMPLATE    = "default";
+
+    /** Name of the file that contains the properties.*/
+
+    public static final String PROPERTYFILE        = "template.properties";
+
+    /** The name under which the resource includes map is stored in the WikiContext. */
+    public static final String RESOURCE_INCLUDES   = "jspwiki.resourceincludes";
+
+    // private Cache              m_propertyCache;
+
+    protected static final Logger log = Logger.getLogger( TemplateManager.class );
+
+    /** Requests a HTTP header.  Value is {@value}. */
+    public static final String RESOURCE_HTTPHEADER = "httpheader";
+
+    /**
+     *  Creates a new TemplateManager.  There is typically one manager per engine.
+     *
+     *  @param engine The owning engine.
+     *  @param properties The property list used to initialize this.
+     */
+    public TemplateManager( WikiEngine engine, Properties properties )
+    {
+        super(engine);
+
+        //
+        //  Uses the unlimited cache.
+        //
+        // m_propertyCache = new Cache( true, false );
+    }
+
+    /**
+     *  Check the existence of a template.
+     */
+    // FIXME: Does not work yet
+    public boolean templateExists( String templateName )
+    {
+        ServletContext context = m_engine.getServletContext();
+
+        InputStream in = context.getResourceAsStream( getPath(templateName)+"ViewTemplate.jsp");
+
+        if( in != null )
+        {
+            try
+            {
+                in.close();
+            }
+            catch( IOException e ) {}
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     *  Tries to locate a given resource from the template directory. If the
+     *  given resource is not found under the current name, returns the
+     *  path to the corresponding one in the default template.
+     *
+     *  @param sContext The servlet context
+     *  @param name The name of the resource
+     *  @return The name of the resource which was found.
+     */
+    private static String findResource( ServletContext sContext, String name )
+    {
+        InputStream is = sContext.getResourceAsStream( name );
+
+        if( is == null )
+        {
+            String defname = makeFullJSPName( DEFAULT_TEMPLATE,
+                                              removeTemplatePart(name) );
+            is = sContext.getResourceAsStream( defname );
+
+            if( is != null )
+                name = defname;
+            else
+                name = null;
+        }
+
+        if( is != null )
+        {
+            try
+            {
+                is.close();
+            }
+            catch( IOException e )
+            {}
+        }
+
+        return name;
+    }
+
+    /**
+     *  Attempts to find a resource from the given template, and if it's not found
+     *  attempts to locate it from the default template.
+     * @param sContext
+     * @param template
+     * @param name
+     * @return
+     */
+    private static String findResource( ServletContext sContext, String template, String name )
+    {
+        if( name.charAt(0) == '/' )
+        {
+            // This is already a full path
+            return findResource( sContext, name );
+        }
+
+        String fullname = makeFullJSPName( template, name );
+
+        return findResource( sContext, fullname );
+    }
+
+    /**
+     *  An utility method for finding a JSP page.  It searches only under
+     *  either current context or by the absolute name.
+     *
+     *  @param pageContext the JSP PageContext
+     *  @param name The name of the JSP page to look for (e.g "Wiki.jsp")
+     *  @return The context path to the resource
+     */
+    public String findJSP( PageContext pageContext, String name )
+    {
+        ServletContext sContext = pageContext.getServletContext();
+
+        return findResource( sContext, name );
+    }
+
+    /**
+     *  Removes the template part of a name.
+     */
+    private static final String removeTemplatePart( String name )
+    {
+        int idx = name.indexOf('/');
+        if( idx != -1 )
+        {
+            idx = name.indexOf('/', idx); // Find second "/"
+
+            if( idx != -1 )
+            {
+                return name.substring( idx+1 );
+            }
+        }
+
+        return name;
+    }
+
+    /**
+     *  Returns the full name (/templates/foo/bar) for name=bar, template=foo.
+     *
+     * @param template
+     * @param name
+     * @return
+     */
+    private static final String makeFullJSPName( String template, String name )
+    {
+        return "/"+DIRECTORY+"/"+template+"/"+name;
+    }
+
+    /**
+     *  Attempts to locate a resource under the given template.  If that template
+     *  does not exist, or the page does not exist under that template, will
+     *  attempt to locate a similarly named file under the default template.
+     *  <p>
+     *  Even though the name suggests only JSP files can be located, but in fact
+     *  this method can find also other resources than JSP files.
+     *
+     *  @param pageContext The JSP PageContext
+     *  @param template From which template we should seek initially?
+     *  @param name Which resource are we looking for (e.g. "ViewTemplate.jsp")
+     *  @return path to the JSP page; null, if it was not found.
+     */
+    public String findJSP( PageContext pageContext, String template, String name )
+    {
+        if( name == null || template == null )
+        {
+            log.fatal("findJSP() was asked to find a null template or name ("+template+","+name+")."+
+                      " JSP page '"+
+                      ((HttpServletRequest)pageContext.getRequest()).getRequestURI()+"'");
+            throw new InternalWikiException("Illegal arguments to findJSP(); please check logs.");
+        }
+
+        return findResource( pageContext.getServletContext(), template, name );
+    }
+
+    /**
+     *  Attempts to locate a resource under the given template.  This matches the
+     *  functionality findJSP(), but uses the WikiContext as the argument.  If there
+     *  is no servlet context (i.e. this is embedded), will just simply return
+     *  a best-guess.
+     *  <p>
+     *  This method is typically used to locate any resource, including JSP pages, images,
+     *  scripts, etc.
+     *
+     *  @since 2.6
+     *  @param ctx the wiki context
+     *  @param template the name of the template to use
+     *  @param name the name of the resource to fine
+     *  @return the path to the resource
+     */
+    public String findResource( WikiContext ctx, String template, String name )
+    {
+        if( m_engine.getServletContext() != null )
+        {
+            return findResource( m_engine.getServletContext(), template, name );
+        }
+
+        return getPath(template)+"/"+name;
+    }
+
+    /**
+     *  Returns a property, as defined in the template.  The evaluation
+     *  is lazy, i.e. the properties are not loaded until the template is
+     *  actually used for the first time.
+     */
+    /*
+    public String getTemplateProperty( WikiContext context, String key )
+    {
+        String template = context.getTemplate();
+
+        try
+        {
+            Properties props = (Properties)m_propertyCache.getFromCache( template, -1 );
+
+            if( props == null )
+            {
+                try
+                {
+                    props = getTemplateProperties( template );
+
+                    m_propertyCache.putInCache( template, props );
+                }
+                catch( IOException e )
+                {
+                    log.warn("IO Exception while reading template properties",e);
+
+                    return null;
+                }
+            }
+
+            return props.getProperty( key );
+        }
+        catch( NeedsRefreshException ex )
+        {
+            // FIXME
+            return null;
+        }
+    }
+*/
+    /**
+     *  Returns an absolute path to a given template.
+     */
+    private static final String getPath( String template )
+    {
+        return "/"+DIRECTORY+"/"+template+"/";
+    }
+
+    /**
+     *   Lists the skins available under this template.  Returns an
+     *   empty Set, if there are no extra skins available.  Note that
+     *   this method does not check whether there is anything actually
+     *   in the directories, it just lists them.  This may change
+     *   in the future.
+     *
+     *   @param pageContext the JSP PageContext
+     *   @param template The template to search
+     *   @return Set of Strings with the skin names.
+     *   @since 2.3.26
+     */
+    public Set listSkins( PageContext pageContext, String template )
+    {
+        String place = makeFullJSPName( template, SKIN_DIRECTORY );
+
+        ServletContext sContext = pageContext.getServletContext();
+
+        Set skinSet = sContext.getResourcePaths( place );
+        TreeSet resultSet = new TreeSet();
+
+        if( log.isDebugEnabled() ) log.debug( "Listings skins from "+place );
+
+        if( skinSet != null )
+        {
+            String[] skins = {};
+
+            skins = (String[]) skinSet.toArray(skins);
+
+            for( int i = 0; i < skins.length; i++ )
+            {
+                String[] s = StringUtils.split(skins[i],"/");
+
+                if( s.length > 2 && skins[i].endsWith("/") )
+                {
+                    String skinName = s[s.length-1];
+                    resultSet.add( skinName );
+                    if( log.isDebugEnabled() ) log.debug("...adding skin '"+skinName+"'");
+                }
+            }
+        }
+
+        return resultSet;
+    }
+
+    /**
+     *  Always returns a valid property map.
+     */
+    /*
+    private Properties getTemplateProperties( String templateName )
+        throws IOException
+    {
+        Properties p = new Properties();
+
+        ServletContext context = m_engine.getServletContext();
+
+        InputStream propertyStream = context.getResourceAsStream(getPath(templateName)+PROPERTYFILE);
+
+        if( propertyStream != null )
+        {
+            p.load( propertyStream );
+
+            propertyStream.close();
+        }
+        else
+        {
+            log.debug("Template '"+templateName+"' does not have a propertyfile '"+PROPERTYFILE+"'.");
+        }
+
+        return p;
+    }
+*/
+    /**
+     *  Returns the include resources marker for a given type.  This is in a
+     *  HTML or Javascript comment format.
+     *
+     *  @param wiki context
+     *  @param type the marker
+     *  @return the generated marker comment
+     */
+    public static String getMarker(WikiActionBean actionBean, String type )
+    {
+        if( type.equals(RESOURCE_JSLOCALIZEDSTRINGS) )
+        {
+            return getJSLocalizedStrings( actionBean );
+        }
+        else if( type.equals(RESOURCE_JSFUNCTION) )
+        {
+            return "/* INCLUDERESOURCES ("+type+") */";
+        }
+        return "<!-- INCLUDERESOURCES ("+type+") -->";
+    }
+
+    /**
+     *  Extract all i18n strings in the javascript domain. (javascript.*)
+     *  Returns a javascript snippet which defines the LoacalizedStings array.
+     *
+     *  @param wiki context
+     *  @return Javascript snippet which defines the LocaliedStrings array
+     *  @author Dirk Frederickx
+     *  @since 2.5.108
+     */
+    private static String getJSLocalizedStrings( WikiActionBean actionBean )
+    {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append( "var LocalizedStrings = {\n");
+
+        ResourceBundle rb = actionBean.getBundle("templates.default");
+
+        boolean first = true;
+
+        for( Enumeration en = rb.getKeys(); en.hasMoreElements(); )
+        {
+            String key = (String)en.nextElement();
+
+            if( key.startsWith("javascript") )
+            {
+                if( first )
+                {
+                    first = false;
+                }
+                else
+                {
+                    sb.append( ",\n" );
+                }
+                sb.append( "\""+key+"\":\""+rb.getString(key)+"\"" );
+            }
+        }
+        sb.append("\n};\n");
+
+        return( sb.toString() );
+    }
+
+    /**
+     *  Adds a resource request to the current request context.
+     *  The content will be added at the resource-type marker
+     *  (see IncludeResourcesTag) in WikiJSPFilter.
+     *  <p>
+     *  The resources can be of different types.  For RESOURCE_SCRIPT and RESOURCE_STYLESHEET
+     *  this is an URI path to the resource (a script file or an external stylesheet)
+     *  that needs to be included.  For RESOURCE_INLINECSS
+     *  the resource should be something that can be added between &lt;style>&lt;/style> in the
+     *  header file (commonheader.jsp).  For RESOURCE_JSFUNCTION it is the name of the Javascript
+     *  function that should be run at page load.
+     *  <p>
+     *  The IncludeResourceTag inserts code in the template files, which is then filled
+     *  by the WikiFilter after the request has been rendered but not yet sent to the recipient.
+     *  <p>
+     *  Note that ALL resource requests get rendered, so this method does not check if
+     *  the request already exists in the resources.  Therefore, if you have a plugin which
+     *  makes a new resource request every time, you'll end up with multiple resource requests
+     *  rendered.  It's thus a good idea to make this request only once during the page
+     *  life cycle.
+     *
+     *  @param ctx The current wiki context
+     *  @param type What kind of a request should be added?
+     *  @param resource The resource to add.
+     */
+    public static void addResourceRequest( WikiActionBean actionBean, String type, String resource )
+    {
+        HashMap resourcemap = (HashMap) actionBean.getVariable( RESOURCE_INCLUDES );
+
+        if( resourcemap == null )
+        {
+            resourcemap = new HashMap();
+        }
+
+        Vector resources = (Vector) resourcemap.get( type );
+
+        if( resources == null )
+        {
+            resources = new Vector();
+        }
+
+        String resourceString = null;
+
+        if( type.equals(RESOURCE_SCRIPT) )
+        {
+            resourceString = "<script type='text/javascript' src='"+resource+"'></script>";
+        }
+        else if( type.equals(RESOURCE_STYLESHEET) )
+        {
+            resourceString = "<link rel='stylesheet' type='text/css' href='"+resource+"' />";
+        }
+        else if( type.equals(RESOURCE_INLINECSS) )
+        {
+            resourceString = "<style type='text/css'>\n"+resource+"\n</style>\n";
+        }
+        else if( type.equals(RESOURCE_JSFUNCTION) )
+        {
+            resourceString = resource;
+        }
+        else if( type.equals(RESOURCE_HTTPHEADER) )
+        {
+            resourceString = resource;
+        }
+
+        if( resourceString != null )
+        {
+            resources.add( resourceString );
+        }
+
+        log.debug("Request to add a resource: "+resourceString);
+
+        resourcemap.put( type, resources );
+        actionBean.setVariable( RESOURCE_INCLUDES, resourcemap );
+    }
+
+    /**
+     *  Returns resource requests for a particular type.  If there are no resources,
+     *  returns an empty array.
+     *
+     *  @param ctx WikiContext
+     *  @param type The resource request type
+     *  @return a String array for the resource requests
+     */
+
+    public static String[] getResourceRequests( WikiActionBean actionBean, String type )
+    {
+        HashMap hm = (HashMap) actionBean.getVariable( RESOURCE_INCLUDES );
+
+        if( hm == null ) return new String[0];
+
+        Vector resources = (Vector) hm.get( type );
+
+        if( resources == null ) return new String[0];
+
+        String[] res = new String[resources.size()];
+
+        return (String[]) resources.toArray( res );
+    }
+
+    /**
+     *  Returns all those types that have been requested so far.
+     *
+     * @param ctx the wiki context
+     * @return the array of types requested
+     */
+    public static String[] getResourceTypes( WikiActionBean actionBean )
+    {
+        String[] res = new String[0];
+
+        if( actionBean != null )
+        {
+            HashMap hm = (HashMap) actionBean.getVariable( RESOURCE_INCLUDES );
+
+            if( hm != null )
+            {
+                Set keys = hm.keySet();
+
+                res = (String[]) keys.toArray( res );
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     *  Returns an empty collection, since at the moment the TemplateManager
+     *  does not manage any modules.
+     *
+     *  @return {@inheritDoc}
+     */
+    public Collection modules()
+    {
+        return new ArrayList();
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiExceptionHandler.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiExceptionHandler.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiExceptionHandler.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiExceptionHandler.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,18 @@
+package com.ecyrd.jspwiki.ui;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.exception.AutoExceptionHandler;
+
+public class WikiExceptionHandler implements AutoExceptionHandler
+{
+
+    public Resolution handle(Throwable exception, HttpServletRequest req, HttpServletResponse res)
+    {
+        exception.printStackTrace();
+        return null;
+    }
+    
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiInterceptor.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiInterceptor.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiInterceptor.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiInterceptor.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,272 @@
+package com.ecyrd.jspwiki.ui;
+
+import java.lang.reflect.Method;
+import java.security.Permission;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.PageContext;
+
+import net.sourceforge.stripes.action.RedirectResolution;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.controller.*;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiSession;
+import com.ecyrd.jspwiki.action.*;
+import com.ecyrd.jspwiki.auth.AuthorizationManager;
+import com.ecyrd.jspwiki.auth.SessionMonitor;
+
+/**
+ * <p>
+ * Stripes {@link net.sourceforge.stripes.controller.Interceptor} that
+ * instantiates the correct WikiContext associated with JSPs, checks for access,
+ * and redirects users if necessary. The interceptor executes during two stages:
+ * after the second lifecycle stage, <em>aka</em>
+ * {@link net.sourceforge.stripes.controller.LifecycleStage#HandlerResolution},
+ * and around (before and after) the third stage,
+ * {@link net.sourceforge.stripes.controller.LifecycleStage#BindingAndValidation}.
+ * </p>
+ * <p>
+ * WikiInterceptor assumes primary responsibility for making JSPWiki objects
+ * available to JSPs as variables. In particular, when WikiInterceptor fires
+ * during the binding and validation stage, sets the following PageContext
+ * attributes, all in {@link PageContext#REQUEST_SCOPE}:
+ * </p>
+ * <ul>
+ * <li><code>wikiEngine</code> - the {@link com.ecyrd.jspwiki.WikiEngine}</li>
+ * <li><code>wikiSession</code> - the user's
+ * {@link com.ecyrd.jspwiki.WikiSession}</li>
+ * <li><code>wikiActionBean</code> - the
+ * {@link com.ecyrd.jspwiki.action.WikiActionBean} injected by Stripes</li>
+ * <li><code>wikiPage</code> - the {@link com.ecyrd.jspwiki.WikiPage}
+ * associated with the WikiActionBean, or the "front page" if the WikiActionBean
+ * is not a WikiContext</li>
+ * </ul>
+ * <p>
+ * After the intercept method fires, calling classes can obtain the saved
+ * WikiActionBean by calling {@link WikiActionBeanFactory#findActionBean(PageContext)}. This is,
+ * indeed, the recommended method for JSP scriptlet code.
+ * </p>
+ * <p>
+ * Because these objects are saved as attributes, they are available to JSPs as
+ * the Expression Language variables <code>${wikiEngine}</code>,
+ * <code>${wikiSession}</code>, <code>${wikiActionBean}</code> and
+ * <code>${wikiPage}</code>.
+ * </p>
+ * 
+ * @author Andrew Jaquith
+ */
+@Intercepts( { LifecycleStage.HandlerResolution, LifecycleStage.BindingAndValidation })
+public class WikiInterceptor implements Interceptor
+{
+    /**
+     * The PageContext attribute name of the WikiActionBean stored by
+     * WikiInterceptor.
+     */
+    public static final String ATTR_ACTIONBEAN = "wikiActionBean";
+
+    /** The PageContext attribute name of the WikiPage stored by WikiInterceptor. */
+    public static final String ATTR_WIKIPAGE = "wikiPage";
+
+    /**
+     * The PageContext attribute name of the WikiEngine stored by
+     * WikiInterceptor.
+     */
+    public static final String ATTR_WIKIENGINE = "wikiEngine";
+
+    /**
+     * The PageContext attribute name of the WikiSession stored by
+     * WikiInterceptor.
+     */
+    public static final String ATTR_WIKISESSION = "wikiSession";
+
+    private static final Logger log = Logger.getLogger(WikiInterceptor.class);
+
+    /**
+     * Facade method that forwards execution to
+     * {@link #interceptHandlerResolution(ExecutionContext)} or
+     * {@link #interceptBindingAndValidation(ExecutionContext)}, depending on
+     * the Stripes {@link net.sourceforge.stripes.controller.LifecycleStage}.
+     * 
+     * @param context
+     *            the current Stripes execution context
+     * @return a Resolution if the delgate intercept method returns one;
+     *         otherwise, <code>null</code>
+     */
+    public Resolution intercept(ExecutionContext context) throws Exception
+    {
+        switch (context.getLifecycleStage())
+        {
+            case HandlerResolution: {
+                return interceptHandlerResolution(context);
+            }
+            case BindingAndValidation: {
+                return interceptBindingAndValidation(context);
+            }
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * <p>
+     * Intercepts the
+     * {@link net.sourceforge.stripes.controller.LifecycleStage#BindingAndValidation}
+     * lifecycle stage and checks for proper access to the current ActionBean
+     * and target event. The access-checking logic runs after after the rest of
+     * the BindingAndValidation processing logic does, after which point Stripes
+     * has already discovered the correct ActionBean, and bound and validated
+     * its request parameters.
+     * </p>
+     * <p>
+     * To determine if the user is allowed to access the target event method,
+     * the method is examined to see if contains a
+     * {@link com.ecyrd.jspwiki.action.EventPermission}) annotation that
+     * specifies the required {@link java.security.Permission}. If the user
+     * does not possess the Permission -- that is,
+     * {@link com.ecyrd.jspwiki.auth.AuthorizationManager#checkPermission(WikiSession, Permission)}
+     * returns <code>false</code> -- this method returns a RedirectResolution
+     * to the login page, with all current parameters appended.
+     * </p>
+     * <p>
+     * If access is allowed, this method injects the WikiEngine, WikiSession,
+     * resolved WikiActionBean and WikiPage as request-scoped PageContext
+     * attributes, and returns a <code>null</code>. After the objects are
+     * injected, downstream classes like WikiTagBase can use them. The attribute
+     * can also be accessed as variables using the JSP Expression Language
+     * (example: <code>${wikiPage}</code>).
+     * 
+     * @param context
+     *            the current execution context
+     * @return a Resolution if the
+     *         {@link net.sourceforge.stripes.controller.LifecycleStage#HandlerResolution}
+     *         lifecycle stage's normal execution returns one; <code>null</code>
+     *         otherwise
+     * @throws Exception
+     *             if the underlying lifcycle stage's execution throws an
+     *             Exception
+     */
+    protected Resolution interceptBindingAndValidation(ExecutionContext context) throws Exception
+    {
+        // Did the handler resolution stage return a Resolution? If so, bail.
+        Resolution r = context.proceed();
+        if (r != null)
+        {
+            return r;
+        }
+
+        // Get the resolved ActionBean and event handler method
+        WikiActionBean actionBean = (WikiActionBean) context.getActionBean();
+        WikiActionBeanContext beanContext = actionBean.getContext();
+        Method handler = context.getHandler();
+
+        // Does the event handler have a required permission?
+        boolean allowed = true;
+        EventPermissionInfo permInfo = beanContext.getPermissionInfo(handler);
+        if (permInfo != null)
+        {
+            Permission requiredPermission = permInfo.getPermission(actionBean);
+            if (requiredPermission != null)
+            {
+                WikiEngine engine = actionBean.getEngine();
+                AuthorizationManager mgr = engine.getAuthorizationManager();
+                WikiSession wikiSession = actionBean.getWikiSession();
+                allowed = mgr.checkPermission(wikiSession, requiredPermission);
+            }
+        }
+
+        // If not allowed, redirect to login page with all parameters intact;
+        // otherwise proceed
+        if (!allowed)
+        {
+            r = new RedirectResolution(LoginActionBean.class);
+            ((RedirectResolution) r).includeRequestParameters(true);
+            if (log.isDebugEnabled())
+            {
+                log.debug("WikiInterceptor rejected access to ActionBean: " + actionBean + ", method " + handler.getName());
+            }
+            return r;
+        }
+
+        if (log.isDebugEnabled())
+        {
+            log.debug("WikiInterceptor resolved ActionBean: " + actionBean);
+        }
+
+        // If not already set, inject WikiEngine as a request attribute (can be
+        // used later as ${wikiEngine} in EL markup)
+        WikiEngine engine = beanContext.getWikiEngine();
+        HttpServletRequest httpRequest = beanContext.getRequest();
+        httpRequest.setAttribute(ATTR_WIKIENGINE, engine);
+
+        // If not already set, Inject the WikiSession as a request attribute
+        WikiSession wikiSession = SessionMonitor.getInstance(engine).find(httpRequest.getSession());
+        httpRequest.setAttribute(ATTR_WIKISESSION, wikiSession);
+
+        // Stash the WikiActionBean and WikiPage in the PageContext
+        // Note: it probably seems a bit tricky that we're grabbing the
+        // PageContext from Stripes. We happen
+        // to know, thanks to the glories of open source code, that Stripes
+        // calls DispatcherHelper's
+        // setPageContext() method immediately before executing the
+        // BindingAndValidation stage,
+        // in *both* the <stripes:useActionBean> case and the StripesFilter
+        // case.
+        // So, the PageContext safe to grab, and boy are we glad that we can!
+        PageContext pageContext = DispatcherHelper.getPageContext();
+        if (pageContext != null)
+        {
+            // Save ActionBean to the current context
+            WikiActionBeanFactory.saveActionBean(pageContext, actionBean);
+        }
+
+        return null;
+    }
+
+    /**
+     * Intercepts the
+     * {@link net.sourceforge.stripes.controller.LifecycleStage#HandlerResolution}
+     * lifecycle stage and injects
+     * {@link com.ecyrd.jspwiki.action.EventPermissionInfo} objects into the
+     * WikiActionBeanContext if indicated by
+     * {@link com.ecyrd.jspwiki.action.EventPermission} annotations on the
+     * current ActionBean.
+     * 
+     * @param context
+     *            the current execution context
+     * @return a Resolution if the
+     *         {@link net.sourceforge.stripes.controller.LifecycleStage#HandlerResolution}
+     *         lifecycle stage's normal execution returns one; <code>null</code>
+     *         otherwise
+     * @throws Exception
+     *             if the underlying lifcycle stage's execution throws an
+     *             Exception
+     */
+    protected Resolution interceptHandlerResolution(ExecutionContext context) throws Exception
+    {
+        // Did the handler resolution stage return a Resolution? If so, bail.
+        Resolution r = context.proceed();
+        if (r != null)
+        {
+            return r;
+        }
+
+        // Get the ActionBean and ActionBeanContext
+        WikiActionBean actionBean = (WikiActionBean) context.getActionBean();
+        WikiActionBeanContext beanContext = actionBean.getContext();
+
+        // Stash the EventPermissionInfo items for this ActionBean's event
+        // handlers
+        Map<Method, EventPermissionInfo> permMap = EventPermissionInfo.getEventPermissionInfo(actionBean.getClass());
+        for (Map.Entry<Method, EventPermissionInfo> entry : permMap.entrySet())
+        {
+            beanContext.addPermissionInfo(entry.getKey(), entry.getValue());
+        }
+
+        return null;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiJSPFilter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiJSPFilter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiJSPFilter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/ui/WikiJSPFilter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,344 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2006 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.ui;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.action.WikiActionBean;
+import com.ecyrd.jspwiki.event.*;
+import com.ecyrd.jspwiki.util.WatchDog;
+
+/**
+ * This filter goes through the generated page response prior and
+ * places requested resources at the appropriate inclusion markers.
+ * This is done to let dynamic content (e.g. plugins, editors) 
+ * include custom resources, even after the HTML head section is
+ * in fact built. This filter is typically the last filter to execute,
+ * and it <em>must</em> run after servlet or JSP code that performs
+ * redirections or sends error codes (such as access control methods).
+ * <p>
+ * Inclusion markers are placed by the IncludeResourcesTag; the
+ * defult content templates (see .../templates/default/commonheader.jsp)
+ * are configured to do this. As an example, a JavaScript resource marker
+ * is added like this:
+ * <pre>
+ * &lt;wiki:IncludeResources type="script"/&gt;
+ * </pre>
+ * Any code that requires special resources must register a resource
+ * request with the TemplateManager. For example:
+ * <pre>
+ * &lt;wiki:RequestResource type="script" path="scripts/custom.js" /&gt;
+ * </pre>
+ * or programmatically,
+ * <pre>
+ * TemplateManager.addResourceRequest( context, TemplateManager.RESOURCE_SCRIPT, "scripts/customresource.js" );
+ * </pre>
+ * 
+ * @see TemplateManager
+ * @see com.ecyrd.jspwiki.tags.RequestResourceTag
+ */
+public class WikiJSPFilter implements Filter
+{
+    protected static final Logger log = Logger.getLogger( WikiJSPFilter.class );
+    protected WikiEngine m_engine = null;
+
+    public WikiJSPFilter()
+    {
+        super();
+    }
+    
+    public void init(FilterConfig config) throws ServletException
+    {
+        ServletContext context = config.getServletContext();
+        m_engine = WikiEngine.getInstance( context, null );
+    }
+
+    public void destroy()
+    {
+        log.info("WikiJSPFilter destroyed; telling WikiEngine to stop..");
+        m_engine.shutdown();
+    }
+    
+    public void doFilter( ServletRequest  request,
+                          ServletResponse response,
+                          FilterChain     chain )
+        throws ServletException, IOException
+    {
+        WatchDog w = m_engine.getCurrentWatchDog();
+        try
+        {
+            NDC.push( m_engine.getApplicationName()+":"+((HttpServletRequest)request).getRequestURI() );
+
+            w.enterState("Filtering for URL "+((HttpServletRequest)request).getRequestURI(), 90 );
+          
+            HttpServletResponseWrapper responseWrapper = new MyServletResponseWrapper( (HttpServletResponse)response );
+        
+            // fire PAGE_REQUESTED event
+            WikiActionBean actionBean = getWikiActionBean( request );
+            boolean isWikiContext = ( actionBean instanceof WikiContext );
+            if ( isWikiContext )
+            {
+                String pageName = ((WikiContext)actionBean).getPage().getName();
+                fireEvent( WikiPageEvent.PAGE_REQUESTED, pageName );
+            }
+
+            chain.doFilter( request, responseWrapper );
+
+            // The response is now complete. Lets replace the markers now.
+        
+            try
+            {
+                w.enterState( "Delivering response", 30 );
+                String r = filter( actionBean, responseWrapper );
+        
+                //String encoding = "UTF-8";
+                //if( wikiContext != null ) encoding = wikiContext.getEngine().getContentEncoding();
+        
+                // Only now write the (real) response to the client.
+                // response.setContentLength(r.length());
+                // response.setContentType(encoding);
+                
+                response.getWriter().write(r);
+            
+                // Clean up the UI messages and loggers
+                actionBean.getWikiSession().clearMessages();
+
+                // fire PAGE_DELIVERED event
+                if ( isWikiContext )
+                {
+                    String pageName = ((WikiContext)actionBean).getPage().getName();
+                    fireEvent( WikiPageEvent.PAGE_DELIVERED, pageName );
+                }
+
+            }
+            finally
+            {
+                w.exitState();
+            }
+        }
+        finally
+        {
+            w.exitState();
+            NDC.pop();
+            NDC.remove();
+        }
+    }
+
+    /**
+     * Goes through all types and writes the appropriate response.
+     * 
+     * @param actionBean The action bean for the current context
+     * @param string The source string
+     * @return The modified string with all the insertions in place.
+     */
+    private String filter(WikiActionBean actionBean, HttpServletResponse response )
+    {
+        String string = response.toString();
+
+        if( actionBean != null )
+        {
+            String[] resourceTypes = TemplateManager.getResourceTypes( actionBean );
+
+            for( int i = 0; i < resourceTypes.length; i++ )
+            {
+                string = insertResources( actionBean, string, resourceTypes[i] );
+            }
+        
+            //
+            //  Add HTTP header Resource Requests
+            //
+            String[] headers = TemplateManager.getResourceRequests( actionBean,
+                                                                    TemplateManager.RESOURCE_HTTPHEADER );
+        
+            for( int i = 0; i < headers.length; i++ )
+            {
+                String key = headers[i];
+                String value = "";
+                int split = headers[i].indexOf(':');
+                if( split > 0 && split < headers[i].length()-1 )
+                {
+                    key = headers[i].substring( 0, split );
+                    value = headers[i].substring( split+1 );
+                }
+            
+                response.addHeader( key.trim(), value.trim() );
+            }
+        }
+
+        return string;
+    }
+
+    /**
+     *  Inserts whatever resources
+     *  were requested by any plugins or other components for this particular
+     *  type.
+     *  
+     *  @param actionBean The action bean for the current context
+     *  @param string The source string
+     *  @param type Type identifier for insertion
+     *  @return The filtered string.
+     */
+    private String insertResources( WikiActionBean actionBean, String string, String type )
+    {
+        if( actionBean == null )
+        {
+            return string;
+        }
+
+        String marker = TemplateManager.getMarker( actionBean, type );
+        int idx = string.indexOf( marker );
+        
+        if( idx == -1 )
+        {
+            return string;
+        }
+        
+        log.debug("...Inserting...");
+        
+        String[] resources = TemplateManager.getResourceRequests( actionBean, type );
+        
+        StringBuffer concat = new StringBuffer( resources.length * 40 );
+        
+        for( int i = 0; i < resources.length; i++  )
+        {
+            log.debug("...:::"+resources[i]);
+            concat.append( resources[i] );
+        }
+
+        string = TextUtil.replaceString( string, 
+                                         idx, 
+                                         idx+marker.length(), 
+                                         concat.toString() );
+        
+        return string;
+    }
+    
+    /**
+     *  Simple response wrapper that just allows us to gobble through the entire
+     *  response before it's output.
+     *  
+     *  @author jalkanen
+     */
+    private static class MyServletResponseWrapper
+        extends HttpServletResponseWrapper
+    {
+        private CharArrayWriter m_output;
+      
+        /** 
+         *  How large the initial buffer should be.  This should be tuned to achieve
+         *  a balance in speed and memory consumption.
+         */
+        private static final int INIT_BUFFER_SIZE = 4096;
+        
+        public MyServletResponseWrapper( HttpServletResponse r )
+        {
+            super(r);
+            m_output = new CharArrayWriter( INIT_BUFFER_SIZE );
+        }
+
+        /**
+         *  Returns a writer for output; this wraps the internal buffer
+         *  into a PrintWriter.
+         */
+        public PrintWriter getWriter()
+        {
+            return new PrintWriter( m_output );
+        }
+
+        public ServletOutputStream getOutputStream()
+        {
+            return new MyServletOutputStream( m_output );
+        }
+
+        class MyServletOutputStream extends ServletOutputStream
+        {
+            CharArrayWriter m_buffer;
+
+            public MyServletOutputStream(CharArrayWriter aCharArrayWriter)
+            {
+                super();
+                m_buffer = aCharArrayWriter;
+            }
+
+            public void write(int aInt)
+            {
+                m_buffer.write( aInt );
+            }
+
+        }
+        
+        /**
+         *  Returns whatever was written so far into the Writer.
+         */
+        public String toString()
+        {
+            return m_output.toString();
+        }
+    }
+
+    /**
+     *  Looks up the WikiActionBean stored in the request.  This method does not create the
+     *  action bean if it does not exist.
+     *  
+     *  @param request The request to examine
+     *  @return A valid WikiActionBean, or <code>null</code> if one could not be located
+     */
+    protected WikiContext getWikiActionBean(ServletRequest  request)
+    {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+    
+        WikiContext ctx = (WikiContext) httpRequest.getAttribute( WikiInterceptor.ATTR_ACTIONBEAN );
+        
+        return ctx;
+    }
+
+    // events processing .......................................................
+
+
+    /**
+     *  Fires a WikiPageEvent of the provided type and page name
+     *  to all registered listeners of the current WikiEngine.
+     *
+     * @see com.ecyrd.jspwiki.event.WikiPageEvent
+     * @param type       the event type to be fired
+     * @param pagename   the wiki page name as a String
+     */
+    protected final void fireEvent( int type, String pagename )
+    {
+        if ( WikiEventManager.isListening(m_engine) )
+        {
+            WikiEventManager.fireEvent(m_engine,new WikiPageEvent(m_engine,type,pagename));
+        }
+    }
+
+}