001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2003-2006 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.ui;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.util.*;
025:
026: import javax.servlet.ServletContext;
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.jsp.PageContext;
029:
030: import org.apache.commons.lang.StringUtils;
031: import org.apache.log4j.Logger;
032:
033: import com.ecyrd.jspwiki.InternalWikiException;
034: import com.ecyrd.jspwiki.WikiContext;
035: import com.ecyrd.jspwiki.WikiEngine;
036: import com.ecyrd.jspwiki.modules.ModuleManager;
037:
038: /**
039: * This class takes care of managing JSPWiki templates. This class also provides
040: * the ResourceRequest mechanism.
041: *
042: * @since 2.1.62
043: * @author Janne Jalkanen
044: */
045: public class TemplateManager extends ModuleManager {
046: private static final String SKIN_DIRECTORY = "skins";
047:
048: /**
049: * Requests a JavaScript function to be called during window.onload. Value is {@value}.
050: */
051: public static final String RESOURCE_JSFUNCTION = "jsfunction";
052:
053: /**
054: * Requests a JavaScript associative array with all localized strings.
055: */
056: public static final String RESOURCE_JSLOCALIZEDSTRINGS = "jslocalizedstrings";
057:
058: /**
059: * Requests a stylesheet to be inserted. Value is {@value}.
060: */
061: public static final String RESOURCE_STYLESHEET = "stylesheet";
062:
063: /**
064: * Requests a script to be loaded. Value is {@value}.
065: */
066: public static final String RESOURCE_SCRIPT = "script";
067:
068: /**
069: * Requests inlined CSS. Value is {@value}.
070: */
071: public static final String RESOURCE_INLINECSS = "inlinecss";
072:
073: /** The default directory for the properties. Value is {@value}. */
074: public static final String DIRECTORY = "templates";
075:
076: /** The name of the default template. Value is {@value}. */
077: public static final String DEFAULT_TEMPLATE = "default";
078:
079: /** Name of the file that contains the properties.*/
080:
081: public static final String PROPERTYFILE = "template.properties";
082:
083: /** The name under which the resource includes map is stored in the WikiContext. */
084: public static final String RESOURCE_INCLUDES = "jspwiki.resourceincludes";
085:
086: // private Cache m_propertyCache;
087:
088: protected static final Logger log = Logger
089: .getLogger(TemplateManager.class);
090:
091: /** Requests a HTTP header. Value is {@value}. */
092: public static final String RESOURCE_HTTPHEADER = "httpheader";
093:
094: /**
095: * Creates a new TemplateManager. There is typically one manager per engine.
096: *
097: * @param engine The owning engine.
098: * @param properties The property list used to initialize this.
099: */
100: public TemplateManager(WikiEngine engine, Properties properties) {
101: super (engine);
102:
103: //
104: // Uses the unlimited cache.
105: //
106: // m_propertyCache = new Cache( true, false );
107: }
108:
109: /**
110: * Check the existence of a template.
111: */
112: // FIXME: Does not work yet
113: public boolean templateExists(String templateName) {
114: ServletContext context = m_engine.getServletContext();
115:
116: InputStream in = context
117: .getResourceAsStream(getPath(templateName)
118: + "ViewTemplate.jsp");
119:
120: if (in != null) {
121: try {
122: in.close();
123: } catch (IOException e) {
124: }
125:
126: return true;
127: }
128:
129: return false;
130: }
131:
132: /**
133: * Tries to locate a given resource from the template directory. If the
134: * given resource is not found under the current name, returns the
135: * path to the corresponding one in the default template.
136: *
137: * @param sContext The servlet context
138: * @param name The name of the resource
139: * @return The name of the resource which was found.
140: */
141: private static String findResource(ServletContext sContext,
142: String name) {
143: InputStream is = sContext.getResourceAsStream(name);
144:
145: if (is == null) {
146: String defname = makeFullJSPName(DEFAULT_TEMPLATE,
147: removeTemplatePart(name));
148: is = sContext.getResourceAsStream(defname);
149:
150: if (is != null)
151: name = defname;
152: else
153: name = null;
154: }
155:
156: if (is != null) {
157: try {
158: is.close();
159: } catch (IOException e) {
160: }
161: }
162:
163: return name;
164: }
165:
166: /**
167: * Attempts to find a resource from the given template, and if it's not found
168: * attempts to locate it from the default template.
169: * @param sContext
170: * @param template
171: * @param name
172: * @return
173: */
174: private static String findResource(ServletContext sContext,
175: String template, String name) {
176: if (name.charAt(0) == '/') {
177: // This is already a full path
178: return findResource(sContext, name);
179: }
180:
181: String fullname = makeFullJSPName(template, name);
182:
183: return findResource(sContext, fullname);
184: }
185:
186: /**
187: * An utility method for finding a JSP page. It searches only under
188: * either current context or by the absolute name.
189: *
190: * @param pageContext the JSP PageContext
191: * @param name The name of the JSP page to look for (e.g "Wiki.jsp")
192: * @return The context path to the resource
193: */
194: public String findJSP(PageContext pageContext, String name) {
195: ServletContext sContext = pageContext.getServletContext();
196:
197: return findResource(sContext, name);
198: }
199:
200: /**
201: * Removes the template part of a name.
202: */
203: private static final String removeTemplatePart(String name) {
204: int idx = name.indexOf('/');
205: if (idx != -1) {
206: idx = name.indexOf('/', idx); // Find second "/"
207:
208: if (idx != -1) {
209: return name.substring(idx + 1);
210: }
211: }
212:
213: return name;
214: }
215:
216: /**
217: * Returns the full name (/templates/foo/bar) for name=bar, template=foo.
218: *
219: * @param template
220: * @param name
221: * @return
222: */
223: private static final String makeFullJSPName(String template,
224: String name) {
225: return "/" + DIRECTORY + "/" + template + "/" + name;
226: }
227:
228: /**
229: * Attempts to locate a resource under the given template. If that template
230: * does not exist, or the page does not exist under that template, will
231: * attempt to locate a similarly named file under the default template.
232: * <p>
233: * Even though the name suggests only JSP files can be located, but in fact
234: * this method can find also other resources than JSP files.
235: *
236: * @param pageContext The JSP PageContext
237: * @param template From which template we should seek initially?
238: * @param name Which resource are we looking for (e.g. "ViewTemplate.jsp")
239: * @return path to the JSP page; null, if it was not found.
240: */
241: public String findJSP(PageContext pageContext, String template,
242: String name) {
243: if (name == null || template == null) {
244: log
245: .fatal("findJSP() was asked to find a null template or name ("
246: + template
247: + ","
248: + name
249: + ")."
250: + " JSP page '"
251: + ((HttpServletRequest) pageContext
252: .getRequest()).getRequestURI()
253: + "'");
254: throw new InternalWikiException(
255: "Illegal arguments to findJSP(); please check logs.");
256: }
257:
258: return findResource(pageContext.getServletContext(), template,
259: name);
260: }
261:
262: /**
263: * Attempts to locate a resource under the given template. This matches the
264: * functionality findJSP(), but uses the WikiContext as the argument. If there
265: * is no servlet context (i.e. this is embedded), will just simply return
266: * a best-guess.
267: * <p>
268: * This method is typically used to locate any resource, including JSP pages, images,
269: * scripts, etc.
270: *
271: * @since 2.6
272: * @param ctx the wiki context
273: * @param template the name of the template to use
274: * @param name the name of the resource to fine
275: * @return the path to the resource
276: */
277: public String findResource(WikiContext ctx, String template,
278: String name) {
279: if (m_engine.getServletContext() != null) {
280: return findResource(m_engine.getServletContext(), template,
281: name);
282: }
283:
284: return getPath(template) + "/" + name;
285: }
286:
287: /**
288: * Returns a property, as defined in the template. The evaluation
289: * is lazy, i.e. the properties are not loaded until the template is
290: * actually used for the first time.
291: */
292: /*
293: public String getTemplateProperty( WikiContext context, String key )
294: {
295: String template = context.getTemplate();
296:
297: try
298: {
299: Properties props = (Properties)m_propertyCache.getFromCache( template, -1 );
300:
301: if( props == null )
302: {
303: try
304: {
305: props = getTemplateProperties( template );
306:
307: m_propertyCache.putInCache( template, props );
308: }
309: catch( IOException e )
310: {
311: log.warn("IO Exception while reading template properties",e);
312:
313: return null;
314: }
315: }
316:
317: return props.getProperty( key );
318: }
319: catch( NeedsRefreshException ex )
320: {
321: // FIXME
322: return null;
323: }
324: }
325: */
326: /**
327: * Returns an absolute path to a given template.
328: */
329: private static final String getPath(String template) {
330: return "/" + DIRECTORY + "/" + template + "/";
331: }
332:
333: /**
334: * Lists the skins available under this template. Returns an
335: * empty Set, if there are no extra skins available. Note that
336: * this method does not check whether there is anything actually
337: * in the directories, it just lists them. This may change
338: * in the future.
339: *
340: * @param pageContext the JSP PageContext
341: * @param template The template to search
342: * @return Set of Strings with the skin names.
343: * @since 2.3.26
344: */
345: public Set listSkins(PageContext pageContext, String template) {
346: String place = makeFullJSPName(template, SKIN_DIRECTORY);
347:
348: ServletContext sContext = pageContext.getServletContext();
349:
350: Set skinSet = sContext.getResourcePaths(place);
351: TreeSet resultSet = new TreeSet();
352:
353: if (log.isDebugEnabled())
354: log.debug("Listings skins from " + place);
355:
356: if (skinSet != null) {
357: String[] skins = {};
358:
359: skins = (String[]) skinSet.toArray(skins);
360:
361: for (int i = 0; i < skins.length; i++) {
362: String[] s = StringUtils.split(skins[i], "/");
363:
364: if (s.length > 2 && skins[i].endsWith("/")) {
365: String skinName = s[s.length - 1];
366: resultSet.add(skinName);
367: if (log.isDebugEnabled())
368: log.debug("...adding skin '" + skinName + "'");
369: }
370: }
371: }
372:
373: return resultSet;
374: }
375:
376: /**
377: * Always returns a valid property map.
378: */
379: /*
380: private Properties getTemplateProperties( String templateName )
381: throws IOException
382: {
383: Properties p = new Properties();
384:
385: ServletContext context = m_engine.getServletContext();
386:
387: InputStream propertyStream = context.getResourceAsStream(getPath(templateName)+PROPERTYFILE);
388:
389: if( propertyStream != null )
390: {
391: p.load( propertyStream );
392:
393: propertyStream.close();
394: }
395: else
396: {
397: log.debug("Template '"+templateName+"' does not have a propertyfile '"+PROPERTYFILE+"'.");
398: }
399:
400: return p;
401: }
402: */
403: /**
404: * Returns the include resources marker for a given type. This is in a
405: * HTML or Javascript comment format.
406: *
407: * @param wiki context
408: * @param type the marker
409: * @return the generated marker comment
410: */
411: public static String getMarker(WikiContext context, String type) {
412: if (type.equals(RESOURCE_JSLOCALIZEDSTRINGS)) {
413: return getJSLocalizedStrings(context);
414: } else if (type.equals(RESOURCE_JSFUNCTION)) {
415: return "/* INCLUDERESOURCES (" + type + ") */";
416: }
417: return "<!-- INCLUDERESOURCES (" + type + ") -->";
418: }
419:
420: /**
421: * Extract all i18n strings in the javascript domain. (javascript.*)
422: * Returns a javascript snippet which defines the LoacalizedStings array.
423: *
424: * @param wiki context
425: * @return Javascript snippet which defines the LocaliedStrings array
426: * @author Dirk Frederickx
427: * @since 2.5.108
428: */
429: private static String getJSLocalizedStrings(WikiContext context) {
430: StringBuffer sb = new StringBuffer();
431:
432: sb.append("var LocalizedStrings = {\n");
433:
434: ResourceBundle rb = context.getBundle("templates.default");
435:
436: boolean first = true;
437:
438: for (Enumeration en = rb.getKeys(); en.hasMoreElements();) {
439: String key = (String) en.nextElement();
440:
441: if (key.startsWith("javascript")) {
442: if (first) {
443: first = false;
444: } else {
445: sb.append(",\n");
446: }
447: sb.append("\"" + key + "\":\"" + rb.getString(key)
448: + "\"");
449: }
450: }
451: sb.append("\n};\n");
452:
453: return (sb.toString());
454: }
455:
456: /**
457: * Adds a resource request to the current request context.
458: * The content will be added at the resource-type marker
459: * (see IncludeResourcesTag) in WikiJSPFilter.
460: * <p>
461: * The resources can be of different types. For RESOURCE_SCRIPT and RESOURCE_STYLESHEET
462: * this is an URI path to the resource (a script file or an external stylesheet)
463: * that needs to be included. For RESOURCE_INLINECSS
464: * the resource should be something that can be added between <style></style> in the
465: * header file (commonheader.jsp). For RESOURCE_JSFUNCTION it is the name of the Javascript
466: * function that should be run at page load.
467: * <p>
468: * The IncludeResourceTag inserts code in the template files, which is then filled
469: * by the WikiFilter after the request has been rendered but not yet sent to the recipient.
470: * <p>
471: * Note that ALL resource requests get rendered, so this method does not check if
472: * the request already exists in the resources. Therefore, if you have a plugin which
473: * makes a new resource request every time, you'll end up with multiple resource requests
474: * rendered. It's thus a good idea to make this request only once during the page
475: * life cycle.
476: *
477: * @param ctx The current wiki context
478: * @param type What kind of a request should be added?
479: * @param resource The resource to add.
480: */
481: public static void addResourceRequest(WikiContext ctx, String type,
482: String resource) {
483: HashMap resourcemap = (HashMap) ctx
484: .getVariable(RESOURCE_INCLUDES);
485:
486: if (resourcemap == null) {
487: resourcemap = new HashMap();
488: }
489:
490: Vector resources = (Vector) resourcemap.get(type);
491:
492: if (resources == null) {
493: resources = new Vector();
494: }
495:
496: String resourceString = null;
497:
498: if (type.equals(RESOURCE_SCRIPT)) {
499: resourceString = "<script type='text/javascript' src='"
500: + resource + "'></script>";
501: } else if (type.equals(RESOURCE_STYLESHEET)) {
502: resourceString = "<link rel='stylesheet' type='text/css' href='"
503: + resource + "' />";
504: } else if (type.equals(RESOURCE_INLINECSS)) {
505: resourceString = "<style type='text/css'>\n" + resource
506: + "\n</style>\n";
507: } else if (type.equals(RESOURCE_JSFUNCTION)) {
508: resourceString = resource;
509: } else if (type.equals(RESOURCE_HTTPHEADER)) {
510: resourceString = resource;
511: }
512:
513: if (resourceString != null) {
514: resources.add(resourceString);
515: }
516:
517: log.debug("Request to add a resource: " + resourceString);
518:
519: resourcemap.put(type, resources);
520: ctx.setVariable(RESOURCE_INCLUDES, resourcemap);
521: }
522:
523: /**
524: * Returns resource requests for a particular type. If there are no resources,
525: * returns an empty array.
526: *
527: * @param ctx WikiContext
528: * @param type The resource request type
529: * @return a String array for the resource requests
530: */
531:
532: public static String[] getResourceRequests(WikiContext ctx,
533: String type) {
534: HashMap hm = (HashMap) ctx.getVariable(RESOURCE_INCLUDES);
535:
536: if (hm == null)
537: return new String[0];
538:
539: Vector resources = (Vector) hm.get(type);
540:
541: if (resources == null)
542: return new String[0];
543:
544: String[] res = new String[resources.size()];
545:
546: return (String[]) resources.toArray(res);
547: }
548:
549: /**
550: * Returns all those types that have been requested so far.
551: *
552: * @param ctx the wiki context
553: * @return the array of types requested
554: */
555: public static String[] getResourceTypes(WikiContext ctx) {
556: String[] res = new String[0];
557:
558: if (ctx != null) {
559: HashMap hm = (HashMap) ctx.getVariable(RESOURCE_INCLUDES);
560:
561: if (hm != null) {
562: Set keys = hm.keySet();
563:
564: res = (String[]) keys.toArray(res);
565: }
566: }
567:
568: return res;
569: }
570:
571: /**
572: * Returns an empty collection, since at the moment the TemplateManager
573: * does not manage any modules.
574: *
575: * @return {@inheritDoc}
576: */
577: public Collection modules() {
578: return new ArrayList();
579: }
580: }
|