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