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 java.io.IOException;
022import java.io.InputStream;
023import java.text.SimpleDateFormat;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Date;
028import java.util.Enumeration;
029import java.util.HashMap;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.Properties;
035import java.util.ResourceBundle;
036import java.util.Set;
037import java.util.TimeZone;
038import java.util.TreeSet;
039import java.util.Vector;
040
041import javax.servlet.ServletContext;
042import javax.servlet.http.HttpServletRequest;
043import javax.servlet.jsp.PageContext;
044import javax.servlet.jsp.jstl.fmt.LocaleSupport;
045
046import org.apache.commons.lang.StringUtils;
047import org.apache.log4j.Logger;
048import org.apache.wiki.InternalWikiException;
049import org.apache.wiki.WikiContext;
050import org.apache.wiki.WikiEngine;
051import org.apache.wiki.i18n.InternationalizationManager;
052import org.apache.wiki.modules.ModuleManager;
053import org.apache.wiki.modules.WikiModuleInfo;
054import org.apache.wiki.preferences.Preferences;
055import org.apache.wiki.preferences.Preferences.TimeFormat;
056import org.apache.wiki.util.ClassUtil;
057
058
059/**
060 *  This class takes care of managing JSPWiki templates.  This class also provides
061 *  the ResourceRequest mechanism.
062 *
063 *  @since 2.1.62
064 */
065public class TemplateManager extends ModuleManager {
066
067    private static final String SKIN_DIRECTORY = "skins";
068
069    /**
070     * Requests a JavaScript function to be called during window.onload. Value is {@value}.
071     */
072    public static final String RESOURCE_JSFUNCTION = "jsfunction";
073
074    /**
075     * Requests a JavaScript associative array with all localized strings.
076     */
077    public static final String RESOURCE_JSLOCALIZEDSTRINGS = "jslocalizedstrings";
078
079    /**
080     * Requests a stylesheet to be inserted. Value is {@value}.
081     */
082    public static final String RESOURCE_STYLESHEET = "stylesheet";
083
084    /**
085     * Requests a script to be loaded. Value is {@value}.
086     */
087    public static final String RESOURCE_SCRIPT = "script";
088
089    /**
090     * Requests inlined CSS. Value is {@value}.
091     */
092    public static final String RESOURCE_INLINECSS = "inlinecss";
093
094    /** The default directory for the properties. Value is {@value}. */
095    public static final String DIRECTORY = "templates";
096
097    /** The name of the default template. Value is {@value}. */
098    public static final String DEFAULT_TEMPLATE = "default";
099
100    /** Name of the file that contains the properties. */
101
102    public static final String PROPERTYFILE = "template.properties";
103
104    /** Location of I18N Resource bundles, and path prefix and suffixes */
105
106    public static final String I18NRESOURCE_PREFIX = "templates/default_";
107
108    public static final String I18NRESOURCE_SUFFIX = ".properties";
109
110    /** The default (en) RESOURCE name and id. */
111
112    public static final String I18NRESOURCE_EN = "templates/default.properties";
113    public static final String I18NRESOURCE_EN_ID = "en";
114
115    /** I18N string to mark the default locale */
116
117    public static final String I18NDEFAULT_LOCALE = "prefs.user.language.default";
118
119    /** I18N string to mark the server timezone */
120
121    public static final String I18NSERVER_TIMEZONE = "prefs.user.timezone.server";
122
123    /** Prefix of the default timeformat properties. */
124
125    public static final String TIMEFORMATPROPERTIES = "jspwiki.defaultprefs.timeformat.";
126
127    /**
128     * The name under which the resource includes map is stored in the
129     * WikiContext.
130     */
131    public static final String RESOURCE_INCLUDES = "jspwiki.resourceincludes";
132
133    // private Cache m_propertyCache;
134
135    protected static final Logger log = Logger.getLogger(TemplateManager.class);
136
137    /** Requests a HTTP header. Value is {@value}. */
138    public static final String RESOURCE_HTTPHEADER = "httpheader";
139
140    /**
141     *  Creates a new TemplateManager.  There is typically one manager per engine.
142     *
143     *  @param engine The owning engine.
144     *  @param properties The property list used to initialize this.
145     */
146    public TemplateManager( WikiEngine engine, Properties properties )
147    {
148        super(engine);
149
150        //
151        //  Uses the unlimited cache.
152        //
153        // m_propertyCache = new Cache( true, false );
154    }
155
156    /**
157     *  Check the existence of a template.
158     */
159    // FIXME: Does not work yet
160    public boolean templateExists( String templateName )
161    {
162        ServletContext context = m_engine.getServletContext();
163
164        InputStream in = context.getResourceAsStream( getPath(templateName)+"ViewTemplate.jsp");
165
166        if( in != null )
167        {
168            try
169            {
170                in.close();
171            }
172            catch (IOException e)
173            {
174            }
175
176            return true;
177        }
178
179        return false;
180    }
181
182    /**
183     *  Tries to locate a given resource from the template directory. If the
184     *  given resource is not found under the current name, returns the
185     *  path to the corresponding one in the default template.
186     *
187     *  @param sContext The servlet context
188     *  @param name The name of the resource
189     *  @return The name of the resource which was found.
190     */
191    private static String findResource( ServletContext sContext, String name )
192    {
193        InputStream is = sContext.getResourceAsStream( name );
194
195        if( is == null )
196        {
197            String defname = makeFullJSPName( DEFAULT_TEMPLATE,
198                                              removeTemplatePart(name) );
199            is = sContext.getResourceAsStream( defname );
200
201            if( is != null )
202                name = defname;
203            else
204                name = null;
205        }
206
207        if( is != null )
208        {
209            try
210            {
211                is.close();
212            }
213            catch( IOException e )
214            {}
215        }
216
217        return name;
218    }
219
220    /**
221     *  Attempts to find a resource from the given template, and if it's not found
222     *  attempts to locate it from the default template.
223     * @param sContext
224     * @param template
225     * @param name
226     * @return the Resource for the given template and name.
227     */
228    private static String findResource( ServletContext sContext, String template, String name )
229    {
230        if( name.charAt(0) == '/' )
231        {
232            // This is already a full path
233            return findResource( sContext, name );
234        }
235
236        String fullname = makeFullJSPName( template, name );
237
238        return findResource( sContext, fullname );
239    }
240
241    /**
242     *  An utility method for finding a JSP page.  It searches only under
243     *  either current context or by the absolute name.
244     *
245     *  @param pageContext the JSP PageContext
246     *  @param name The name of the JSP page to look for (e.g "Wiki.jsp")
247     *  @return The context path to the resource
248     */
249    public String findJSP( PageContext pageContext, String name )
250    {
251        ServletContext sContext = pageContext.getServletContext();
252
253        return findResource( sContext, name );
254    }
255
256    /**
257     *  Removes the template part of a name.
258     */
259    private static String removeTemplatePart( String name )
260    {
261        int idx = 0;
262        if( name.startsWith( "/" ) ) idx = 1;
263
264        idx = name.indexOf('/', idx);
265        if( idx != -1 )
266        {
267            idx = name.indexOf('/', idx+1); // Find second "/"
268
269            if( idx != -1 )
270            {
271                name = name.substring( idx+1 );
272            }
273        }
274
275        if( log.isDebugEnabled() ) log.debug( "Final name = "+name );
276        return name;
277    }
278
279    /**
280     *  Returns the full name (/templates/foo/bar) for name=bar, template=foo.
281     *
282     * @param template The name of the template.
283     * @param name The name of the resource.
284     * @return The full name for a template.
285     */
286    private static String makeFullJSPName( String template, String name )
287    {
288        return "/"+DIRECTORY+"/"+template+"/"+name;
289    }
290
291    /**
292     *  Attempts to locate a resource under the given template.  If that template
293     *  does not exist, or the page does not exist under that template, will
294     *  attempt to locate a similarly named file under the default template.
295     *  <p>
296     *  Even though the name suggests only JSP files can be located, but in fact
297     *  this method can find also other resources than JSP files.
298     *
299     *  @param pageContext The JSP PageContext
300     *  @param template From which template we should seek initially?
301     *  @param name Which resource are we looking for (e.g. "ViewTemplate.jsp")
302     *  @return path to the JSP page; null, if it was not found.
303     */
304    public String findJSP( PageContext pageContext, String template, String name )
305    {
306        if( name == null || template == null )
307        {
308            log.fatal("findJSP() was asked to find a null template or name ("+template+","+name+")."+
309                      " JSP page '"+
310                      ((HttpServletRequest)pageContext.getRequest()).getRequestURI()+"'");
311            throw new InternalWikiException("Illegal arguments to findJSP(); please check logs.");
312        }
313
314        return findResource( pageContext.getServletContext(), template, name );
315    }
316
317    /**
318     *  Attempts to locate a resource under the given template.  This matches the
319     *  functionality findJSP(), but uses the WikiContext as the argument.  If there
320     *  is no servlet context (i.e. this is embedded), will just simply return
321     *  a best-guess.
322     *  <p>
323     *  This method is typically used to locate any resource, including JSP pages, images,
324     *  scripts, etc.
325     *
326     *  @since 2.6
327     *  @param ctx the wiki context
328     *  @param template the name of the template to use
329     *  @param name the name of the resource to fine
330     *  @return the path to the resource
331     */
332    public String findResource( WikiContext ctx, String template, String name )
333    {
334        if( m_engine.getServletContext() != null )
335        {
336            return findResource( m_engine.getServletContext(), template, name );
337        }
338
339        return getPath(template)+"/"+name;
340    }
341
342    /**
343     *  Returns a property, as defined in the template.  The evaluation
344     *  is lazy, i.e. the properties are not loaded until the template is
345     *  actually used for the first time.
346     */
347    /*
348    public String getTemplateProperty( WikiContext context, String key )
349    {
350        String template = context.getTemplate();
351
352        try
353        {
354            Properties props = (Properties)m_propertyCache.getFromCache( template, -1 );
355
356            if( props == null )
357            {
358                try
359                {
360                    props = getTemplateProperties( template );
361
362                    m_propertyCache.putInCache( template, props );
363                }
364                catch( IOException e )
365                {
366                    log.warn("IO Exception while reading template properties",e);
367
368                    return null;
369                }
370            }
371
372            return props.getProperty( key );
373        }
374        catch( NeedsRefreshException ex )
375        {
376            // FIXME
377            return null;
378        }
379    }
380*/
381    /**
382     *  Returns an absolute path to a given template.
383     */
384    private static String getPath( String template )
385    {
386        return "/"+DIRECTORY+"/"+template+"/";
387    }
388
389    /**
390     *   Lists the skins available under this template.  Returns an
391     *   empty Set, if there are no extra skins available.  Note that
392     *   this method does not check whether there is anything actually
393     *   in the directories, it just lists them.  This may change
394     *   in the future.
395     *
396     *   @param pageContext the JSP PageContext
397     *   @param template The template to search
398     *   @return Set of Strings with the skin names.
399     *   @since 2.3.26
400     */
401    @SuppressWarnings("unchecked")
402    public Set listSkins( PageContext pageContext, String template )
403    {
404        String place = makeFullJSPName( template, SKIN_DIRECTORY );
405
406        ServletContext sContext = pageContext.getServletContext();
407
408        Set<String> skinSet = sContext.getResourcePaths( place );
409        TreeSet<String> resultSet = new TreeSet<String>();
410
411        if( log.isDebugEnabled() ) log.debug( "Listings skins from "+place );
412
413        if( skinSet != null )
414        {
415            String[] skins = {};
416
417            skins = skinSet.toArray(skins);
418
419            for (int i = 0; i < skins.length; i++)
420            {
421                String[] s = StringUtils.split(skins[i], "/");
422
423                if (s.length > 2 && skins[i].endsWith("/"))
424                {
425                    String skinName = s[s.length - 1];
426                    resultSet.add(skinName);
427                    if (log.isDebugEnabled())
428                        log.debug("...adding skin '" + skinName + "'");
429                }
430            }
431        }
432
433        return resultSet;
434    }
435
436    /**
437     * List all installed i18n language properties by classpath searching for files like :
438     *    templates/default_*.properties
439     *    templates/default.properties
440     *
441     * @param pageContext
442     * @return map of installed Languages
443     * @since 2.7.x
444     */
445    public Map listLanguages(PageContext pageContext)
446    {
447        Map< String, String > resultMap = new LinkedHashMap< String, String >();
448        String clientLanguage = ((HttpServletRequest) pageContext.getRequest()).getLocale().toString();
449
450        List< String > entries = ClassUtil.classpathEntriesUnder( DIRECTORY );
451        for( String name : entries ) {
452            if ( name.equals( I18NRESOURCE_EN ) ||
453                    (name.startsWith( I18NRESOURCE_PREFIX ) && name.endsWith( I18NRESOURCE_SUFFIX ) ) )
454            {
455                if (name.equals( I18NRESOURCE_EN )) {
456                    name = I18NRESOURCE_EN_ID;
457                }    else {
458                    name = name.substring(I18NRESOURCE_PREFIX.length(), name.lastIndexOf(I18NRESOURCE_SUFFIX));
459                }
460                Locale locale = new Locale(name.substring(0, 2), ((name.indexOf("_") == -1) ? "" : name.substring(3, 5)));
461                String defaultLanguage = "";
462                if (clientLanguage.startsWith(name))
463                {
464                    defaultLanguage = LocaleSupport.getLocalizedMessage(pageContext, I18NDEFAULT_LOCALE);
465                }
466                resultMap.put(name, locale.getDisplayName(locale) + " " + defaultLanguage);
467            }
468        }
469
470        return resultMap;
471    }
472
473
474    /**
475     * List all available timeformats, read from the jspwiki.properties
476     *
477     * @param pageContext
478     * @return map of TimeFormats
479     * @since 2.7.x
480     */
481    public Map listTimeFormats(PageContext pageContext)
482    {
483        WikiContext context = WikiContext.findContext( pageContext );
484        Properties props = m_engine.getWikiProperties();
485        ArrayList<String> tfArr = new ArrayList<String>(40);
486        LinkedHashMap<String,String> resultMap = new LinkedHashMap<String,String>();
487
488        /* filter timeformat properties */
489        for (Enumeration e = props.propertyNames(); e.hasMoreElements();)
490        {
491            String name = (String) e.nextElement();
492
493            if (name.startsWith(TIMEFORMATPROPERTIES))
494            {
495                tfArr.add(name);
496            }
497        }
498
499        /* fetch actual formats */
500        if (tfArr.size() == 0) /*
501                                 * no props found - make sure some default
502                                 * formats are avail
503                                 */
504        {
505            tfArr.add("dd-MMM-yy");
506            tfArr.add("d-MMM-yyyy");
507            tfArr.add("EEE, dd-MMM-yyyy, zzzz");
508        }
509        else
510        {
511            Collections.sort(tfArr);
512
513            for (int i = 0; i < tfArr.size(); i++)
514            {
515                tfArr.set(i, props.getProperty(tfArr.get(i)));
516            }
517        }
518
519        String prefTimeZone = Preferences.getPreference( context, "TimeZone" );
520        //TimeZone tz = TimeZone.getDefault();
521        TimeZone tz = TimeZone.getTimeZone(prefTimeZone);
522        /*try
523        {
524            tz.setRawOffset(Integer.parseInt(prefTimeZone));
525        }
526        catch (Exception e)
527        {
528        }*/
529
530        Date d = new Date(); // current date
531        try
532        {
533            // dummy format pattern
534            SimpleDateFormat fmt = Preferences.getDateFormat( context, TimeFormat.DATETIME );
535            fmt.setTimeZone(tz);
536
537            for (int i = 0; i < tfArr.size(); i++)
538            {
539                try
540                {
541                    String f = tfArr.get(i);
542                    fmt.applyPattern(f);
543
544                    resultMap.put(f, fmt.format(d));
545                }
546                catch (IllegalArgumentException e)
547                {
548                } // skip parameter
549            }
550        }
551        catch (IllegalArgumentException e)
552        {
553        } // skip parameter
554
555        return resultMap;
556    }
557
558    /**
559     * List all timezones, with special marker for server timezone
560     *
561     * @param pageContext
562     * @return map of TimeZones
563     * @since 2.7.x
564     */
565    public Map listTimeZones(PageContext pageContext)
566    {
567        LinkedHashMap<String,String> resultMap = new LinkedHashMap<String,String>();
568
569        String[][] tzs = { { "GMT-12", "Enitwetok, Kwajalien" },
570                          { "GMT-11", "Nome, Midway Island, Samoa" },
571                          { "GMT-10", "Hawaii" },
572                          { "GMT-9", "Alaska" },
573                          { "GMT-8", "Pacific Time" },
574                          { "GMT-7", "Mountain Time" },
575                          { "GMT-6", "Central Time, Mexico City" },
576                          { "GMT-5", "Eastern Time, Bogota, Lima, Quito" },
577                          { "GMT-4", "Atlantic Time, Caracas, La Paz" },
578                          { "GMT-3:30", "Newfoundland" },
579                          { "GMT-3", "Brazil, Buenos Aires, Georgetown, Falkland Is." },
580                          { "GMT-2", "Mid-Atlantic, Ascention Is., St Helena" },
581                          { "GMT-1", "Azores, Cape Verde Islands" },
582                          { "GMT", "Casablanca, Dublin, Edinburgh, London, Lisbon, Monrovia" },
583                          { "GMT+1", "Berlin, Brussels, Copenhagen, Madrid, Paris, Rome" },
584                          { "GMT+2", "Helsinki, Athens, Kaliningrad, South Africa, Warsaw" },
585                          { "GMT+3", "Baghdad, Riyadh, Moscow, Nairobi" },
586                          { "GMT+3:30", "Tehran" },
587                          { "GMT+4", "Adu Dhabi, Baku, Muscat, Tbilisi" },
588                          { "GMT+4:30", "Kabul" },
589                          { "GMT+5", "Islamabad, Karachi, Tashkent" },
590                          { "GMT+5:30", "Bombay, Calcutta, Madras, New Delhi" },
591                          { "GMT+6", "Almaty, Colomba, Dhakra" },
592                          { "GMT+7", "Bangkok, Hanoi, Jakarta" },
593                          { "GMT+8", "Beijing, Hong Kong, Perth, Singapore, Taipei" },
594                          { "GMT+9", "Osaka, Sapporo, Seoul, Tokyo, Yakutsk" },
595                          { "GMT+9:30", "Adelaide, Darwin" },
596                          { "GMT+10", "Melbourne, Papua New Guinea, Sydney, Vladivostok" },
597                          { "GMT+11", "Magadan, New Caledonia, Solomon Islands" },
598                          { "GMT+12", "Auckland, Wellington, Fiji, Marshall Island" } };
599
600        java.util.TimeZone servertz = java.util.TimeZone.getDefault();
601
602        for( int i = 0; i < tzs.length; i++ )
603        {
604            String tzID = tzs[i][0];
605            java.util.TimeZone tz = java.util.TimeZone.getTimeZone(tzID);
606
607            String serverTimeZone = "";
608
609            if( servertz.getRawOffset() == tz.getRawOffset() )
610            {
611                serverTimeZone = LocaleSupport.getLocalizedMessage(pageContext, I18NSERVER_TIMEZONE);
612                tzID = servertz.getID();
613            }
614
615            resultMap.put(tzID, "(" + tzs[i][0] + ") "+tzs[i][1] + " " + serverTimeZone);
616        }
617
618        return resultMap;
619    }
620
621    /**
622     *  Always returns a valid property map.
623     */
624    /*
625    private Properties getTemplateProperties( String templateName )
626        throws IOException
627    {
628        Properties p = new Properties();
629
630        ServletContext context = m_engine.getServletContext();
631
632        InputStream propertyStream = context.getResourceAsStream(getPath(templateName)+PROPERTYFILE);
633
634        if( propertyStream != null )
635        {
636            p.load( propertyStream );
637
638            propertyStream.close();
639        }
640        else
641        {
642            log.debug("Template '"+templateName+"' does not have a propertyfile '"+PROPERTYFILE+"'.");
643        }
644
645        return p;
646    }
647*/
648    /**
649     *  Returns the include resources marker for a given type.  This is in a
650     *  HTML or Javascript comment format.
651     *
652     *  @param context the wiki context
653     *  @param type the marker
654     *  @return the generated marker comment
655     */
656    public static String getMarker(WikiContext context, String type )
657    {
658        if( type.equals(RESOURCE_JSLOCALIZEDSTRINGS) )
659        {
660            return getJSLocalizedStrings( context );
661        }
662        else if( type.equals(RESOURCE_JSFUNCTION) )
663        {
664            return "/* INCLUDERESOURCES ("+type+") */";
665        }
666        return "<!-- INCLUDERESOURCES ("+type+") -->";
667    }
668
669    /**
670     *  Extract all i18n strings in the javascript domain. (javascript.*)
671     *  Returns a javascript snippet which defines the LocalizedStings array.
672     *
673     *  @param context the {@link WikiContext}
674     *  @return Javascript snippet which defines the LocalizedStrings array
675     *  @since 2.5.108
676     */
677    private static String getJSLocalizedStrings( WikiContext context )
678    {
679        StringBuilder sb = new StringBuilder();
680
681        sb.append( "var LocalizedStrings = {\n");
682
683        ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.DEF_TEMPLATE );
684
685        boolean first = true;
686
687        for( Enumeration< String > en = rb.getKeys(); en.hasMoreElements(); )
688        {
689            String key = en.nextElement();
690
691            if( key.startsWith("javascript") )
692            {
693                if( first )
694                {
695                    first = false;
696                }
697                else
698                {
699                    sb.append( ",\n" );
700                }
701                sb.append( "\""+key+"\":\""+rb.getString(key)+"\"" );
702            }
703        }
704        sb.append("\n};\n");
705
706        return( sb.toString() );
707    }
708
709    /**
710     *  Adds a resource request to the current request context.
711     *  The content will be added at the resource-type marker
712     *  (see IncludeResourcesTag) in WikiJSPFilter.
713     *  <p>
714     *  The resources can be of different types.  For RESOURCE_SCRIPT and RESOURCE_STYLESHEET
715     *  this is an URI path to the resource (a script file or an external stylesheet)
716     *  that needs to be included.  For RESOURCE_INLINECSS
717     *  the resource should be something that can be added between &lt;style>&lt;/style> in the
718     *  header file (commonheader.jsp).  For RESOURCE_JSFUNCTION it is the name of the Javascript
719     *  function that should be run at page load.
720     *  <p>
721     *  The IncludeResourceTag inserts code in the template files, which is then filled
722     *  by the WikiFilter after the request has been rendered but not yet sent to the recipient.
723     *  <p>
724     *  Note that ALL resource requests get rendered, so this method does not check if
725     *  the request already exists in the resources.  Therefore, if you have a plugin which
726     *  makes a new resource request every time, you'll end up with multiple resource requests
727     *  rendered.  It's thus a good idea to make this request only once during the page
728     *  life cycle.
729     *
730     *  @param ctx The current wiki context
731     *  @param type What kind of a request should be added?
732     *  @param resource The resource to add.
733     */
734    @SuppressWarnings("unchecked")
735    public static void addResourceRequest( WikiContext ctx, String type, String resource )
736    {
737        HashMap<String,Vector<String>> resourcemap = (HashMap<String,Vector<String>>) ctx.getVariable( RESOURCE_INCLUDES );
738
739        if( resourcemap == null )
740        {
741            resourcemap = new HashMap<String,Vector<String>>();
742        }
743
744        Vector<String> resources = resourcemap.get( type );
745
746        if( resources == null )
747        {
748            resources = new Vector<String>();
749        }
750
751        String resourceString = null;
752
753        if( type.equals(RESOURCE_SCRIPT) )
754        {
755            resourceString = "<script type='text/javascript' src='"+resource+"'></script>";
756        }
757        else if( type.equals(RESOURCE_STYLESHEET) )
758        {
759            resourceString = "<link rel='stylesheet' type='text/css' href='"+resource+"' />";
760        }
761        else if( type.equals(RESOURCE_INLINECSS) )
762        {
763            resourceString = "<style type='text/css'>\n"+resource+"\n</style>\n";
764        }
765        else if( type.equals(RESOURCE_JSFUNCTION) )
766        {
767            resourceString = resource;
768        }
769        else if( type.equals(RESOURCE_HTTPHEADER) )
770        {
771            resourceString = resource;
772        }
773
774        if( resourceString != null )
775        {
776            resources.add( resourceString );
777        }
778
779        log.debug("Request to add a resource: "+resourceString);
780
781        resourcemap.put( type, resources );
782        ctx.setVariable( RESOURCE_INCLUDES, resourcemap );
783    }
784
785    /**
786     *  Returns resource requests for a particular type.  If there are no resources,
787     *  returns an empty array.
788     *
789     *  @param ctx WikiContext
790     *  @param type The resource request type
791     *  @return a String array for the resource requests
792     */
793
794    @SuppressWarnings("unchecked")
795    public static String[] getResourceRequests( WikiContext ctx, String type )
796    {
797        HashMap<String,Vector<String>> hm = (HashMap<String,Vector<String>>) ctx.getVariable( RESOURCE_INCLUDES );
798
799        if( hm == null ) return new String[0];
800
801        Vector<String> resources = hm.get( type );
802
803        if( resources == null ) return new String[0];
804
805        String[] res = new String[resources.size()];
806
807        return resources.toArray( res );
808    }
809
810    /**
811     *  Returns all those types that have been requested so far.
812     *
813     * @param ctx the wiki context
814     * @return the array of types requested
815     */
816    @SuppressWarnings("unchecked")
817    public static String[] getResourceTypes( WikiContext ctx )
818    {
819        String[] res = new String[0];
820
821        if( ctx != null )
822        {
823            HashMap<String,String> hm = (HashMap<String,String>) ctx.getVariable( RESOURCE_INCLUDES );
824
825            if( hm != null )
826            {
827                Set<String> keys = hm.keySet();
828
829                res = keys.toArray( res );
830            }
831        }
832
833        return res;
834    }
835
836    /**
837     *  Returns an empty collection, since at the moment the TemplateManager
838     *  does not manage any modules.
839     *
840     *  @return {@inheritDoc}
841     */
842    @Override
843    public Collection modules()
844    {
845        return new ArrayList<WikiModuleInfo>();
846    }
847
848    /**
849     *  Returns null!
850     *  {@inheritDoc}
851     */
852    @Override
853    public WikiModuleInfo getModuleInfo(String moduleName) {
854        return null;
855    }
856}