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.ui;
020
021import org.apache.commons.lang3.StringUtils;
022import org.apache.logging.log4j.LogManager;
023import org.apache.logging.log4j.Logger;
024import org.apache.wiki.InternalWikiException;
025import org.apache.wiki.api.core.Context;
026import org.apache.wiki.api.core.Engine;
027import org.apache.wiki.modules.BaseModuleManager;
028import org.apache.wiki.modules.WikiModuleInfo;
029import org.apache.wiki.preferences.Preferences;
030import org.apache.wiki.preferences.Preferences.TimeFormat;
031
032import javax.servlet.ServletContext;
033import javax.servlet.http.HttpServletRequest;
034import javax.servlet.jsp.PageContext;
035import java.io.IOException;
036import java.io.InputStream;
037import java.text.SimpleDateFormat;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.Date;
042import java.util.Enumeration;
043import java.util.LinkedHashMap;
044import java.util.Map;
045import java.util.Properties;
046import java.util.Set;
047import java.util.TimeZone;
048import java.util.TreeSet;
049
050
051/**
052 *  This class takes care of managing JSPWiki templates.  This class also provides the ResourceRequest mechanism.
053 *
054 *  @since 2.1.62
055 */
056public class DefaultTemplateManager extends BaseModuleManager implements TemplateManager {
057
058    private static final Logger log = LogManager.getLogger( DefaultTemplateManager.class );
059
060    /**
061     *  Creates a new TemplateManager.  There is typically one manager per engine.
062     *
063     *  @param engine The owning engine.
064     *  @param properties The property list used to initialize this.
065     */
066    public DefaultTemplateManager( final Engine engine, final Properties properties ) {
067        super( engine );
068    }
069
070    /** {@inheritDoc} */
071    @Override
072    // FIXME: Does not work yet
073    public boolean templateExists( final String templateName ) {
074        final ServletContext context = m_engine.getServletContext();
075        try( final InputStream in = context.getResourceAsStream( getPath( templateName ) + "ViewTemplate.jsp" ) ) {
076            if( in != null ) {
077                return true;
078            }
079        } catch( final IOException e ) {
080            log.error( e.getMessage(), e );
081        }
082        return false;
083    }
084
085    /**
086     *  Tries to locate a given resource from the template directory. If the given resource is not found under the current name, returns the
087     *  path to the corresponding one in the default template.
088     *
089     *  @param sContext The servlet context
090     *  @param name The name of the resource
091     *  @return The name of the resource which was found.
092     */
093    private static String findResource( final ServletContext sContext, final String name ) {
094        String resourceName = name;
095        try( final InputStream is = sContext.getResourceAsStream( resourceName ) ) {
096            if( is == null ) {
097                final String defname = makeFullJSPName( DEFAULT_TEMPLATE, removeTemplatePart( resourceName ) );
098                try( final InputStream iis = sContext.getResourceAsStream( defname ) ) {
099                    resourceName = iis != null ? defname : null;
100                }
101            }
102        } catch( final IOException e ) {
103            log.error( "unable to open " + name + " as resource stream", e );
104        }
105        return resourceName;
106    }
107
108    /**
109     *  Attempts to find a resource from the given template, and if it's not found
110     *  attempts to locate it from the default template.
111     * @param sContext servlet context used to search for the resource
112     * @param template template used to search for the resource
113     * @param name resource name
114     * @return the Resource for the given template and name.
115     */
116    private static String findResource( final ServletContext sContext, final String template, final String name ) {
117        if( name.charAt(0) == '/' ) {
118            // This is already a full path
119            return findResource( sContext, name );
120        }
121        final String fullname = makeFullJSPName( template, name );
122        return findResource( sContext, fullname );
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public String findJSP( final PageContext pageContext, final String name ) {
128        final ServletContext sContext = pageContext.getServletContext();
129        return findResource( sContext, name );
130    }
131
132    /**
133     *  Removes the template part of a name.
134     */
135    private static String removeTemplatePart( String name ) {
136        int idx = 0;
137        if( name.startsWith( "/" ) ) {
138            idx = 1;
139        }
140
141        idx = name.indexOf('/', idx);
142        if( idx != -1 ) {
143            idx = name.indexOf('/', idx+1); // Find second "/"
144            if( idx != -1 ) {
145                name = name.substring( idx+1 );
146            }
147        }
148
149        if( log.isDebugEnabled() ) {
150            log.debug( "Final name = "+name );
151        }
152        return name;
153    }
154
155    /**
156     *  Returns the full name (/templates/foo/bar) for name=bar, template=foo.
157     *
158     * @param template The name of the template.
159     * @param name The name of the resource.
160     * @return The full name for a template.
161     */
162    private static String makeFullJSPName( final String template, final String name ) {
163        return "/" + DIRECTORY + "/" + template + "/" + name;
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    public String findJSP( final PageContext pageContext, final String template, final String name ) {
169        if( name == null || template == null ) {
170            log.fatal("findJSP() was asked to find a null template or name (" + template + "," + name + ")." + " JSP page '" +
171                      ( ( HttpServletRequest )pageContext.getRequest() ).getRequestURI() + "'" );
172            throw new InternalWikiException( "Illegal arguments to findJSP(); please check logs." );
173        }
174
175        return findResource( pageContext.getServletContext(), template, name );
176    }
177
178    /** {@inheritDoc} */
179    @Override
180    public String findResource( final Context ctx, final String template, final String name ) {
181        if( m_engine.getServletContext() != null ) {
182            return findResource( m_engine.getServletContext(), template, name );
183        }
184
185        return getPath( template ) + "/" + name;
186    }
187
188    /*
189     *  Returns a property, as defined in the template.  The evaluation is lazy, i.e. the properties are not loaded until the template is
190     *  actually used for the first time.
191     */
192    /*
193    public String getTemplateProperty( WikiContext context, String key )
194    {
195        String template = context.getTemplate();
196
197        try
198        {
199            Properties props = (Properties)m_propertyCache.getFromCache( template, -1 );
200
201            if( props == null )
202            {
203                try
204                {
205                    props = getTemplateProperties( template );
206
207                    m_propertyCache.putInCache( template, props );
208                }
209                catch( IOException e )
210                {
211                    log.warn("IO Exception while reading template properties",e);
212
213                    return null;
214                }
215            }
216
217            return props.getProperty( key );
218        }
219        catch( NeedsRefreshException ex )
220        {
221            // FIXME
222            return null;
223        }
224    }
225*/
226    /**
227     *  Returns an absolute path to a given template.
228     */
229    private static String getPath( final String template ) {
230        return "/" + DIRECTORY + "/" + template + "/";
231    }
232
233    /** {@inheritDoc} */
234    @Override
235    public Set< String > listSkins( final PageContext pageContext, final String template ) {
236        final String place = makeFullJSPName( template, SKIN_DIRECTORY );
237        final ServletContext sContext = pageContext.getServletContext();
238        final Set< String > skinSet = sContext.getResourcePaths( place );
239        final Set< String > resultSet = new TreeSet<>();
240
241        if( log.isDebugEnabled() ) {
242            log.debug( "Listings skins from " + place );
243        }
244
245        if( skinSet != null ) {
246            final String[] skins = skinSet.toArray( new String[]{} );
247            for( final String skin : skins ) {
248                final String[] s = StringUtils.split( skin, "/" );
249                if( s.length > 2 && skin.endsWith( "/" ) ) {
250                    final String skinName = s[ s.length - 1 ];
251                    resultSet.add( skinName );
252                    if( log.isDebugEnabled() ) {
253                        log.debug( "...adding skin '" + skinName + "'" );
254                    }
255                }
256            }
257        }
258
259        return resultSet;
260    }
261
262
263    /** {@inheritDoc} */
264    @Override
265    public Map< String, String > listTimeFormats( final PageContext pageContext ) {
266        final Context context = Context.findContext( pageContext );
267        final Properties props = m_engine.getWikiProperties();
268        final ArrayList< String > tfArr = new ArrayList<>(40);
269        final LinkedHashMap< String, String > resultMap = new LinkedHashMap<>();
270
271        /* filter timeformat properties */
272        for( final Enumeration< ? > e = props.propertyNames(); e.hasMoreElements(); ) {
273            final String name = ( String )e.nextElement();
274            if( name.startsWith( TIMEFORMATPROPERTIES ) ) {
275                tfArr.add( name );
276            }
277        }
278
279        /* fetch actual formats */
280        if( tfArr.size() == 0 )  {/* no props found - make sure some default formats are avail */
281            tfArr.add( "dd-MMM-yy" );
282            tfArr.add( "d-MMM-yyyy" );
283            tfArr.add( "EEE, dd-MMM-yyyy, zzzz" );
284        } else {
285            Collections.sort( tfArr );
286
287            for (int i = 0; i < tfArr.size(); i++) {
288                tfArr.set(i, props.getProperty(tfArr.get(i)));
289            }
290        }
291
292        final String prefTimeZone = Preferences.getPreference( context, "TimeZone" );
293        final TimeZone tz = TimeZone.getTimeZone( prefTimeZone );
294
295        final Date d = new Date(); // current date
296        try {
297            // dummy format pattern
298            final SimpleDateFormat fmt = Preferences.getDateFormat( context, TimeFormat.DATETIME );
299            fmt.setTimeZone( tz );
300
301            for( final String s : tfArr ) {
302                try {
303                    fmt.applyPattern( s );
304                    resultMap.put( s, fmt.format( d ) );
305                } catch( final IllegalArgumentException e ) {
306                } // skip parameter
307            }
308        } catch( final IllegalArgumentException e ) {} // skip parameter
309
310        return resultMap;
311    }
312
313    /*
314     *  Always returns a valid property map.
315     */
316    /*
317    private Properties getTemplateProperties( String templateName )
318        throws IOException
319    {
320        Properties p = new Properties();
321
322        ServletContext context = m_engine.getServletContext();
323
324        InputStream propertyStream = context.getResourceAsStream(getPath(templateName)+PROPERTYFILE);
325
326        if( propertyStream != null )
327        {
328            p.load( propertyStream );
329
330            propertyStream.close();
331        }
332        else
333        {
334            log.debug("Template '"+templateName+"' does not have a propertyfile '"+PROPERTYFILE+"'.");
335        }
336
337        return p;
338    }
339*/
340
341    /** {@inheritDoc} */
342    @Override
343    public Collection< WikiModuleInfo > modules() {
344        return new ArrayList<>();
345    }
346
347    /** {@inheritDoc} */
348    @Override
349    public WikiModuleInfo getModuleInfo( final String moduleName ) {
350        return null;
351    }
352
353}