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        log.debug( "Final name = {}", name );
150        return name;
151    }
152
153    /**
154     *  Returns the full name (/templates/foo/bar) for name=bar, template=foo.
155     *
156     * @param template The name of the template.
157     * @param name The name of the resource.
158     * @return The full name for a template.
159     */
160    private static String makeFullJSPName( final String template, final String name ) {
161        return "/" + DIRECTORY + "/" + template + "/" + name;
162    }
163
164    /** {@inheritDoc} */
165    @Override
166    public String findJSP( final PageContext pageContext, final String template, final String name ) {
167        if( name == null || template == null ) {
168            log.fatal("findJSP() was asked to find a null template or name (" + template + "," + name + ")." + " JSP page '" +
169                      ( ( HttpServletRequest )pageContext.getRequest() ).getRequestURI() + "'" );
170            throw new InternalWikiException( "Illegal arguments to findJSP(); please check logs." );
171        }
172
173        return findResource( pageContext.getServletContext(), template, name );
174    }
175
176    /** {@inheritDoc} */
177    @Override
178    public String findResource( final Context ctx, final String template, final String name ) {
179        if( m_engine.getServletContext() != null ) {
180            return findResource( m_engine.getServletContext(), template, name );
181        }
182
183        return getPath( template ) + "/" + name;
184    }
185
186    /**
187     *  Returns an absolute path to a given template.
188     */
189    private static String getPath( final String template ) {
190        return "/" + DIRECTORY + "/" + template + "/";
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public Set< String > listSkins( final PageContext pageContext, final String template ) {
196        final String place = makeFullJSPName( template, SKIN_DIRECTORY );
197        final ServletContext sContext = pageContext.getServletContext();
198        final Set< String > skinSet = sContext.getResourcePaths( place );
199        final Set< String > resultSet = new TreeSet<>();
200
201        log.debug( "Listings skins from {}", place );
202        if( skinSet != null ) {
203            final String[] skins = skinSet.toArray( new String[]{} );
204            for( final String skin : skins ) {
205                final String[] s = StringUtils.split( skin, "/" );
206                if( s.length > 2 && skin.endsWith( "/" ) ) {
207                    final String skinName = s[ s.length - 1 ];
208                    resultSet.add( skinName );
209                    log.debug( "...adding skin '{}'", skinName );
210                }
211            }
212        }
213
214        return resultSet;
215    }
216
217    /** {@inheritDoc} */
218    @Override
219    public Map< String, String > listTimeFormats( final PageContext pageContext ) {
220        final Context context = Context.findContext( pageContext );
221        final Properties props = m_engine.getWikiProperties();
222        final ArrayList< String > tfArr = new ArrayList<>(40);
223        final LinkedHashMap< String, String > resultMap = new LinkedHashMap<>();
224
225        /* filter timeformat properties */
226        for( final Enumeration< ? > e = props.propertyNames(); e.hasMoreElements(); ) {
227            final String name = ( String )e.nextElement();
228            if( name.startsWith( TIMEFORMATPROPERTIES ) ) {
229                tfArr.add( name );
230            }
231        }
232
233        /* fetch actual formats */
234        if( tfArr.size() == 0 )  {/* no props found - make sure some default formats are avail */
235            tfArr.add( "dd-MMM-yy" );
236            tfArr.add( "d-MMM-yyyy" );
237            tfArr.add( "EEE, dd-MMM-yyyy, zzzz" );
238        } else {
239            Collections.sort( tfArr );
240
241            for (int i = 0; i < tfArr.size(); i++) {
242                tfArr.set(i, props.getProperty(tfArr.get(i)));
243            }
244        }
245
246        final String prefTimeZone = Preferences.getPreference( context, "TimeZone" );
247        final TimeZone tz = TimeZone.getTimeZone( prefTimeZone );
248
249        final Date d = new Date(); // current date
250        try {
251            // dummy format pattern
252            final SimpleDateFormat fmt = Preferences.getDateFormat( context, TimeFormat.DATETIME );
253            fmt.setTimeZone( tz );
254
255            for( final String s : tfArr ) {
256                try {
257                    fmt.applyPattern( s );
258                    resultMap.put( s, fmt.format( d ) );
259                } catch( final IllegalArgumentException e ) {
260                } // skip parameter
261            }
262        } catch( final IllegalArgumentException e ) {} // skip parameter
263
264        return resultMap;
265    }
266
267    /** {@inheritDoc} */
268    @Override
269    public Collection< WikiModuleInfo > modules() {
270        return new ArrayList<>();
271    }
272
273    /** {@inheritDoc} */
274    @Override
275    public WikiModuleInfo getModuleInfo( final String moduleName ) {
276        return null;
277    }
278
279}