001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018 */
019package org.apache.wiki.plugin;
020
021import org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023import org.apache.wiki.api.core.Context;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.api.core.Page;
026import org.apache.wiki.api.exceptions.PluginException;
027import org.apache.wiki.api.exceptions.RedirectException;
028import org.apache.wiki.api.exceptions.WikiException;
029import org.apache.wiki.api.plugin.Plugin;
030import org.apache.wiki.api.spi.Wiki;
031import org.apache.wiki.pages.PageManager;
032import org.apache.wiki.parser.MarkupParser;
033import org.apache.wiki.preferences.Preferences;
034
035import java.io.PrintWriter;
036import java.io.StringWriter;
037import java.security.Principal;
038import java.text.MessageFormat;
039import java.text.SimpleDateFormat;
040import java.util.Date;
041import java.util.Map;
042import java.util.Properties;
043import java.util.ResourceBundle;
044import java.util.StringTokenizer;
045
046/**
047 *  Provides a handler for bug reports.  Still under construction.
048 *
049 *  <p>Parameters : </p>
050 *  <ul>
051 *  <li><b>title</b> -  title of the bug, this is required.  If it is empty (as in "")  it is a signal to the handler to return quietly.</li>
052 *  <li><b>description</b> - description of the bug.</li>
053 *  <li><b>version</b> - version</li>
054 *  <li><b>map</b> - I have no idea </li>
055 *  <li><b>page</b> - The name of the page to be created for this bug report </li>
056 *  </ul>
057 *
058 */
059public class BugReportHandler implements Plugin {
060
061    private static final Logger log = LogManager.getLogger( BugReportHandler.class );
062    private static final String DEFAULT_DATEFORMAT = "dd-MMM-yyyy HH:mm:ss zzz";
063
064    /** Parameter name for setting the title.  Value is <tt>{@value}</tt>. */
065    public static final String PARAM_TITLE          = "title";
066    /** Parameter name for setting the description.  Value is <tt>{@value}</tt>. */
067    public static final String PARAM_DESCRIPTION    = "description";
068    /** Parameter name for setting the version.  Value is <tt>{@value}</tt>. */
069    public static final String PARAM_VERSION        = "version";
070    /** Parameter name for setting the map.  Value is <tt>{@value}</tt>. */
071    public static final String PARAM_MAPPINGS       = "map";
072    /** Parameter name for setting the page.  Value is <tt>{@value}</tt>. */
073    public static final String PARAM_PAGE           = "page";
074
075    /**
076     *  {@inheritDoc}
077     */
078    @Override
079    public String execute( final Context context, final Map< String, String > params ) throws PluginException {
080        final String title = params.get( PARAM_TITLE );
081        String description = params.get( PARAM_DESCRIPTION );
082        String version = params.get( PARAM_VERSION );
083        String submitter = null;
084        final SimpleDateFormat format = new SimpleDateFormat( DEFAULT_DATEFORMAT );
085        final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE );
086        final Principal wup = context.getCurrentUser();
087
088        if( wup != null ) {
089            submitter = wup.getName();
090        }
091
092        if( title == null ) {
093            throw new PluginException(rb.getString("bugreporthandler.titlerequired"));
094        }
095        if( title.isEmpty() ) {
096            return "";
097        }
098
099        if( description == null ) {
100            description = "";
101        }
102        if( version == null ) {
103            version = "unknown";
104        }
105
106        final Properties mappings = parseMappings( params.get( PARAM_MAPPINGS ) );
107
108        //  Start things
109        try {
110            final StringWriter str = new StringWriter();
111            final PrintWriter out = new PrintWriter( str );
112            final Date d = new Date();
113
114            //  Outputting of basic data
115            out.println( "|" + mappings.getProperty(PARAM_TITLE,"Title" ) + "|" + title );
116            out.println( "|" + mappings.getProperty("date","Date" ) + "|" + format.format( d ) );
117            out.println( "|" + mappings.getProperty(PARAM_VERSION,"Version" ) + "|" + version );
118            if( submitter != null ) {
119                out.println("|"+mappings.getProperty("submitter","Submitter") + "|" + submitter );
120            }
121
122            //  Outputting the other parameters added to this.
123            for( final Map.Entry< String, String > entry : params.entrySet() ) {
124                if( !( entry.getKey().equals( PARAM_TITLE ) ||
125                       entry.getKey().equals( PARAM_DESCRIPTION ) ||
126                       entry.getKey().equals( PARAM_VERSION ) ||
127                       entry.getKey().equals( PARAM_MAPPINGS ) ||
128                       entry.getKey().equals( PARAM_PAGE ) ||
129                       entry.getKey().startsWith( "_" )
130                     ) ) {
131                    //  If no mapping has been defined, just ignore it.
132                    final String head = mappings.getProperty( entry.getKey(), entry.getKey() );
133                    if( !head.isEmpty() ) {
134                        out.println( "|" + head + "|" + entry.getValue() );
135                    }
136                }
137            }
138
139            out.println();
140            out.println( description );
141            out.close();
142
143            //  Now create a new page for this bug report
144            final String pageName = findNextPage( context, title, params.get( PARAM_PAGE ) );
145            final Page newPage = Wiki.contents().page( context.getEngine(), pageName );
146            final Context newContext = context.clone();
147            newContext.setPage( newPage );
148            context.getEngine().getManager( PageManager.class ).saveText( newContext, str.toString() );
149
150            final MessageFormat formatter = new MessageFormat("");
151            formatter.applyPattern( rb.getString("bugreporthandler.new") );
152            final String[] args = { "<a href=\""+context.getViewURL(pageName)+"\">"+pageName+"</a>" };
153
154            return formatter.format( args );
155        } catch( final RedirectException e ) {
156            log.info("Saving not allowed, reason: '"+e.getMessage()+"', can't redirect to "+e.getRedirect());
157            throw new PluginException("Saving not allowed, reason: "+e.getMessage());
158        } catch( final WikiException e ) {
159            log.error( "Unable to save page!", e );
160            return rb.getString("bugreporthandler.unable" );
161        }
162    }
163
164    /**
165     *  Finds a free page name for adding the bug report.  Tries to construct a page, and if it's found, adds a number to it
166     *  and tries again.
167     */
168    private synchronized String findNextPage( final Context context, final String title, final String baseName ) {
169        final String basicPageName = ( ( baseName != null ) ? baseName : "Bug" ) + MarkupParser.cleanLink( title );
170        final Engine engine = context.getEngine();
171
172        String pageName = basicPageName;
173        long   lastbug  = 2;
174        while( engine.getManager( PageManager.class ).wikiPageExists( pageName ) ) {
175            pageName = basicPageName + lastbug++;
176        }
177
178        return pageName;
179    }
180
181    /**
182     *  Just parses a mappings list in the form of "a=b;b=c;c=d".
183     *  <p>
184     *  FIXME: Should probably be in TextUtil or somewhere.
185     */
186    private Properties parseMappings( final String mappings ) {
187        final Properties props = new Properties();
188        if( mappings == null ) {
189            return props;
190        }
191
192        final StringTokenizer tok = new StringTokenizer( mappings, ";" );
193        while( tok.hasMoreTokens() ) {
194            final String t = tok.nextToken();
195            final int colon = t.indexOf("=");
196            final String key;
197            final String value;
198
199            if( colon > 0 ) {
200                key = t.substring(0,colon);
201                value = t.substring(colon+1);
202            } else {
203                key = t;
204                value = "";
205            }
206
207            props.setProperty( key, value );
208        }
209        return props;
210    }
211
212}