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