1 /*
2 * $Header: /home/cvs/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/JellyContext.java,v 1.10 2002/04/26 12:20:12 jstrachan Exp $
3 * $Revision: 1.10 $
4 * $Date: 2002/04/26 12:20:12 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * $Id: JellyContext.java,v 1.10 2002/04/26 12:20:12 jstrachan Exp $
61 */
62 package org.apache.commons.jelly;
63
64 import java.io.File;
65 import java.io.InputStream;
66 import java.net.MalformedURLException;
67 import java.net.URL;
68 import java.util.Hashtable;
69 import java.util.Iterator;
70 import java.util.Map;
71
72 import org.apache.commons.jelly.parser.XMLParser;
73 import org.apache.commons.logging.Log;
74 import org.apache.commons.logging.LogFactory;
75
76 /*** <p><code>JellyContext</code> represents the Jelly context.</p>
77 *
78 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
79 * @version $Revision: 1.10 $
80 */
81 public class JellyContext {
82
83 /*** The root URL context (where scripts are located from) */
84 private URL rootURL;
85
86 /*** The current URL context (where relative scripts are located from) */
87 private URL currentURL;
88
89 /*** Tag libraries found so far */
90 private Map taglibs = new Hashtable();
91
92 /*** synchronized access to the variables in scope */
93 private Map variables = new Hashtable();
94
95 /*** The Log to which logging calls will be made. */
96 private Log log = LogFactory.getLog(JellyContext.class);
97
98 /*** The parent context */
99 private JellyContext parent;
100
101 /*** Do we inherit variables from parent context? */
102 private boolean inherit = false;
103
104 /*** Do we export our variables to parent context? */
105 private boolean export = false;
106
107 /*** Should we export tag libraries to our parents context */
108 private boolean exportLibraries = true;
109
110 /*** Should we cache Tag instances, per thread, to reduce object contruction overhead? */
111 private boolean cacheTags = false;
112
113
114 /***
115 * The class loader to use for instantiating application objects.
116 * If not specified, the context class loader, or the class loader
117 * used to load this class itself, is used, based on the value of the
118 * <code>useContextClassLoader</code> variable.
119 */
120 protected ClassLoader classLoader;
121
122 /***
123 * Do we want to use the Context ClassLoader when loading classes
124 * for instantiating new objects? Default is <code>false</code>.
125 */
126 protected boolean useContextClassLoader = false;
127
128
129 public JellyContext() {
130 this.currentURL = rootURL;
131 init();
132 }
133
134 public JellyContext(URL rootURL) {
135 this( rootURL, rootURL );
136 }
137
138 public JellyContext(URL rootURL, URL currentURL) {
139 this.rootURL = rootURL;
140 this.currentURL = currentURL;
141 init();
142 }
143
144 public JellyContext(JellyContext parent) {
145 this.parent = parent;
146 this.rootURL = parent.rootURL;
147 this.currentURL = parent.currentURL;
148 this.variables.put("parentScope", parent.variables);
149 this.cacheTags = cacheTags;
150 init();
151 }
152
153 public JellyContext(JellyContext parentJellyContext, URL currentURL) {
154 this(parentJellyContext);
155 this.currentURL = currentURL;
156 }
157
158 public JellyContext(JellyContext parentJellyContext, URL rootURL, URL currentURL) {
159 this(parentJellyContext, currentURL);
160 this.rootURL = rootURL;
161 }
162
163 private void init() {
164 variables.put("context", this);
165 variables.put("systemScope", System.getProperties());
166 }
167
168 /***
169 * @return the parent context for this context
170 */
171 public JellyContext getParent() {
172 return parent;
173 }
174
175 /***
176 * @return the scope of the given name, such as the 'parent' scope.
177 * If Jelly is used in a Servlet situation then 'request', 'session' and 'application' are other names
178 * for scopes
179 */
180 public JellyContext getScope(String name) {
181 if ( "parent".equals( name ) ) {
182 return getParent();
183 }
184 return null;
185 }
186
187 /***
188 * Finds the variable value of the given name in this context or in any other parent context.
189 * If this context does not contain the variable, then its parent is used and then its parent
190 * and so forth until the context with no parent is found.
191 *
192 * @return the value of the variable in this or one of its descendant contexts or null
193 * if the variable could not be found.
194 */
195 public Object findVariable(String name) {
196 Object answer = variables.get(name);
197 if ( answer == null && parent != null ) {
198 answer = parent.findVariable(name);
199 }
200 // ### this is a hack - remove this when we have support for pluggable Scopes
201 if ( answer == null ) {
202 try {
203 answer = System.getProperty(name);
204 }
205 catch (Throwable t) {
206 // ignore security exceptions
207 }
208 }
209 return answer;
210 }
211
212
213 /*** @return the value of the given variable name */
214 public Object getVariable(String name) {
215 Object value = variables.get(name);
216
217 if ( value == null
218 &&
219 isInherit() ) {
220 value = getParent().findVariable( name );
221 }
222
223 return value;
224 }
225
226 /***
227 * @return the value of the given variable name in the given variable scope
228 * @param name is the name of the variable
229 * @param scopeName is the optional scope name such as 'parent'. For servlet environments
230 * this could be 'application', 'session' or 'request'.
231 */
232 public Object getVariable(String name, String scopeName) {
233 JellyContext scope = getScope(scopeName);
234 if ( scope != null ) {
235 return scope.getVariable(name);
236 }
237 return null;
238 }
239
240
241
242 /*** Sets the value of the given variable name */
243 public void setVariable(String name, Object value) {
244 if ( isExport() ) {
245 getParent().setVariable( name, value );
246 return;
247 }
248 if (value == null) {
249 variables.remove(name);
250 }
251 else {
252 variables.put(name, value);
253 }
254 }
255
256 /***
257 * Sets the value of the given variable name in the given variable scope
258 * @param name is the name of the variable
259 * @param scopeName is the optional scope name such as 'parent'. For servlet environments
260 * this could be 'application', 'session' or 'request'.
261 * @param value is the value of the attribute
262 */
263 public void setVariable(String name, String scopeName, Object value) {
264 JellyContext scope = getScope(scopeName);
265 if ( scope != null ) {
266 scope.setVariable(name, value);
267 }
268 }
269
270 /*** Removes the given variable */
271 public void removeVariable(String name) {
272 variables.remove(name);
273 }
274
275 /***
276 * Removes the given variable in the specified scope.
277 *
278 * @param name is the name of the variable
279 * @param scopeName is the optional scope name such as 'parent'. For servlet environments
280 * this could be 'application', 'session' or 'request'.
281 * @param value is the value of the attribute
282 */
283 public void removeVariable(String name, String scopeName) {
284 JellyContext scope = getScope(scopeName);
285 if ( scope != null ) {
286 scope.removeVariable(name);
287 }
288 }
289
290 /***
291 * @return an Iterator over the current variable names in this
292 * context
293 */
294 public Iterator getVariableNames() {
295 return variables.keySet().iterator();
296 }
297
298 /***
299 * @return the Map of variables in this scope
300 */
301 public Map getVariables() {
302 return variables;
303 }
304
305 /***
306 * Sets the Map of variables to use
307 */
308
309 public void setVariables(Map variables) {
310 this.variables = variables;
311 }
312
313 /***
314 * A factory method to create a new child context of the
315 * current context.
316 */
317 public JellyContext newJellyContext(Map newVariables) {
318 // XXXX: should allow this new context to
319 // XXXX: inherit parent contexts?
320 // XXXX: Or at least publish the parent scope
321 // XXXX: as a Map in this new variable scope?
322 newVariables.put("parentScope", variables);
323 JellyContext answer = createChildContext();
324 answer.setVariables(newVariables);
325 return answer;
326 }
327
328 /*** Registers the given tag library against the given namespace URI.
329 * This should be called before the parser is used.
330 */
331 public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
332 if (log.isDebugEnabled()) {
333 log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
334 }
335 taglibs.put(namespaceURI, taglib);
336
337 if (isExportLibraries() && parent != null) {
338 parent.registerTagLibrary( namespaceURI, taglib );
339 }
340
341 if (isExportLibraries() && parent != null) {
342 parent.registerTagLibrary( namespaceURI, taglib );
343 }
344 }
345
346 /*** Registers the given tag library class name against the given namespace URI.
347 * The class will be loaded via the given ClassLoader
348 * This should be called before the parser is used.
349 */
350 public void registerTagLibrary(
351 String namespaceURI,
352 String className) {
353
354 if (log.isDebugEnabled()) {
355 log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
356 }
357 taglibs.put(namespaceURI, className);
358
359 if (isExportLibraries() && parent != null) {
360 parent.registerTagLibrary( namespaceURI, className );
361 }
362 }
363
364 public boolean isTagLibraryRegistered(String namespaceURI) {
365 boolean answer = taglibs.containsKey( namespaceURI );
366 if (answer) {
367 return true;
368 }
369 else if ( parent != null ) {
370 return parent.isTagLibraryRegistered(namespaceURI);
371 }
372 else {
373 return false;
374 }
375 }
376
377 /***
378 * @return the TagLibrary for the given namespace URI or null if one could not be found
379 */
380 public TagLibrary getTagLibrary(String namespaceURI) {
381
382 // use my own mapping first, so that namespaceURIs can
383 // be redefined inside child contexts...
384
385 Object answer = taglibs.get(namespaceURI);
386
387 if ( answer == null && parent != null ) {
388 answer = parent.getTagLibrary( namespaceURI );
389 }
390
391 if ( answer instanceof TagLibrary ) {
392 return (TagLibrary) answer;
393 }
394 else if ( answer instanceof String ) {
395 String className = (String) answer;
396 Class theClass = null;
397 try {
398 theClass = getClassLoader().loadClass(className);
399 }
400 catch (ClassNotFoundException e) {
401 log.error("Could not find the class: " + className, e);
402 }
403 if ( theClass != null ) {
404 try {
405 Object object = theClass.newInstance();
406 if (object instanceof TagLibrary) {
407 taglibs.put(namespaceURI, object);
408 return (TagLibrary) object;
409 }
410 else {
411 log.error(
412 "The tag library object mapped to: "
413 + namespaceURI
414 + " is not a TagLibrary. Object = "
415 + object);
416 }
417 }
418 catch (Exception e) {
419 log.error(
420 "Could not instantiate instance of class: " + className + ". Reason: " + e,
421 e);
422 }
423 }
424 }
425
426 return null;
427 }
428
429 /***
430 * Attempts to parse the script from the given uri using the
431 * {@link #getResource} method then returns the compiled script.
432 */
433 public Script compileScript(String uri) throws Exception {
434 XMLParser parser = new XMLParser();
435 parser.setContext(this);
436 InputStream in = getResourceAsStream(uri);
437 if (in == null) {
438 throw new JellyException("Could not find Jelly script: " + uri);
439 }
440 Script script = parser.parse(in);
441 return script.compile();
442 }
443
444 /***
445 * Attempts to parse the script from the given URL using the
446 * {@link #getResource} method then returns the compiled script.
447 */
448 public Script compileScript(URL url) throws Exception {
449 XMLParser parser = new XMLParser();
450 parser.setContext(this);
451 Script script = parser.parse(url.toString());
452 return script.compile();
453 }
454
455 /***
456 * Parses the script from the given File then compiles it and runs it.
457 *
458 * @return the new child context that was used to run the script
459 */
460 public JellyContext runScript(File file, XMLOutput output) throws Exception {
461 return runScript(file.toURL(), output);
462 }
463
464 /***
465 * Parses the script from the given URL then compiles it and runs it.
466 *
467 * @return the new child context that was used to run the script
468 */
469 public JellyContext runScript(URL url, XMLOutput output) throws Exception {
470 Script script = compileScript(url);
471
472 URL newJellyContextURL = getJellyContextURL(url);
473 JellyContext newJellyContext = new JellyContext(this, newJellyContextURL);
474 script.run(newJellyContext, output);
475 return newJellyContext;
476 }
477
478 /***
479 * Parses the script from the given uri using the
480 * JellyContext.getResource() API then compiles it and runs it.
481 *
482 * @return the new child context that was used to run the script
483 */
484 public JellyContext runScript(String uri, XMLOutput output) throws Exception {
485 URL url = getResource(uri);
486 if (url == null) {
487 throw new JellyException("Could not find Jelly script: " + url);
488 }
489 Script script = compileScript(url);
490
491 URL newJellyContextURL = getJellyContextURL(url);
492 JellyContext newJellyContext = new JellyContext(this, newJellyContextURL);
493 script.run(newJellyContext, output);
494 return newJellyContext;
495 }
496
497 /***
498 * Parses the script from the given uri using the
499 * JellyContext.getResource() API then compiles it and runs it.
500 *
501 * @return the new child context that was used to run the script
502 */
503 public JellyContext runScript(String uri, XMLOutput output,
504 boolean export, boolean inherit) throws Exception {
505 URL url = getResource(uri);
506 if (url == null) {
507 throw new JellyException("Could not find Jelly script: " + url);
508 }
509 Script script = compileScript(url);
510
511 URL newJellyContextURL = getJellyContextURL(url);
512
513 JellyContext newJellyContext = new JellyContext(this, newJellyContextURL);
514 newJellyContext.setExport( export );
515 newJellyContext.setInherit( inherit );
516
517 if ( inherit ) {
518 // use the same variable scopes
519 newJellyContext.variables = this.variables;
520 }
521
522 script.run(newJellyContext, output);
523
524 return newJellyContext;
525 }
526
527 /***
528 * Returns a URL for the given resource from the specified path.
529 * If the uri starts with "/" then the path is taken as relative to
530 * the current context root. If the uri is a well formed URL then it
531 * is used. Otherwise the uri is interpreted as relative to the current
532 * context (the location of the current script).
533 */
534 public URL getResource(String uri) throws MalformedURLException {
535 if (uri.startsWith("/")) {
536 // append this uri to the context root
537 return createRelativeURL(rootURL, uri.substring(1));
538 }
539 else {
540 try {
541 return new URL(uri);
542 }
543 catch (MalformedURLException e) {
544 // lets try find a relative resource
545 try {
546 return createRelativeURL(currentURL, uri);
547 }
548 catch (MalformedURLException e2) {
549 throw e;
550 }
551 }
552 }
553 }
554
555 /***
556 * Attempts to open an InputStream to the given resource at the specified path.
557 * If the uri starts with "/" then the path is taken as relative to
558 * the current context root. If the uri is a well formed URL then it
559 * is used. Otherwise the uri is interpreted as relative to the current
560 * context (the location of the current script).
561 *
562 * @return null if this resource could not be loaded, otherwise the resources
563 * input stream is returned.
564 */
565 public InputStream getResourceAsStream(String uri) {
566 try {
567 URL url = getResource(uri);
568 return url.openStream();
569 }
570 catch (Exception e) {
571 if (log.isTraceEnabled()) {
572 log.trace(
573 "Caught exception attempting to open: " + uri + ". Exception: " + e,
574 e);
575 }
576 return null;
577 }
578 }
579
580
581 // Properties
582 //-------------------------------------------------------------------------
583
584 /***
585 * @return the current root context URL from which all absolute resource URIs
586 * will be relative to. For example in a web application the root URL will
587 * map to the web directory which contains the WEB-INF directory.
588 */
589 public URL getRootURL() {
590 return rootURL;
591 }
592
593 /***
594 * Sets the current root context URL from which all absolute resource URIs
595 * will be relative to. For example in a web application the root URL will
596 * map to the web directory which contains the WEB-INF directory.
597 */
598 public void setRootURL(URL rootURL) {
599 this.rootURL = rootURL;
600 }
601
602
603 /***
604 * @return the current URL context of the current script that is executing.
605 * This URL context is used to deduce relative scripts when relative URIs are
606 * used in calls to {@link #getResource} to process relative scripts.
607 */
608 public URL getCurrentURL() {
609 return currentURL;
610 }
611
612 /***
613 * Sets the current URL context of the current script that is executing.
614 * This URL context is used to deduce relative scripts when relative URIs are
615 * used in calls to {@link #getResource} to process relative scripts.
616 */
617 public void setCurrentURL(URL currentURL) {
618 this.currentURL = currentURL;
619 }
620
621 /***
622 * Returns whether caching of Tag instances, per thread, is enabled.
623 * Caching Tags can boost performance, on some JVMs, by reducing the cost of
624 * object construction when running Jelly inside a multi-threaded application server
625 * such as a Servlet engine.
626 *
627 * @return whether caching of Tag instances is enabled.
628 */
629 public boolean isCacheTags() {
630 return cacheTags;
631 }
632
633 /***
634 * Sets whether caching of Tag instances, per thread, is enabled.
635 * Caching Tags can boost performance, on some JVMs, by reducing the cost of
636 * object construction when running Jelly inside a multi-threaded application server
637 * such as a Servlet engine.
638 *
639 * @param cacheTags Whether caching should be enabled or disabled.
640 */
641 public void setCacheTags(boolean cacheTags) {
642 this.cacheTags = cacheTags;
643 }
644
645 /***
646 * Returns whether we export tag libraries to our parents context
647 * @return boolean
648 */
649 public boolean isExportLibraries() {
650 return exportLibraries;
651 }
652
653 /***
654 * Sets whether we export tag libraries to our parents context
655 * @param exportLibraries The exportLibraries to set
656 */
657 public void setExportLibraries(boolean exportLibraries) {
658 this.exportLibraries = exportLibraries;
659 }
660
661
662 /***
663 * Sets whether we should export variable definitions to our parent context
664 */
665 public void setExport(boolean export) {
666 this.export = export;
667 }
668
669 public boolean isExport() {
670 return this.export;
671 }
672
673 /***
674 * Sets whether we should inherit variables from our parent context
675 */
676 public void setInherit(boolean inherit) {
677 this.inherit = inherit;
678 }
679
680 public boolean isInherit() {
681 return this.inherit;
682 }
683
684
685 /***
686 * Return the class loader to be used for instantiating application objects
687 * when required. This is determined based upon the following rules:
688 * <ul>
689 * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
690 * <li>The thread context class loader, if it exists and the
691 * <code>useContextClassLoader</code> property is set to true</li>
692 * <li>The class loader used to load the XMLParser class itself.
693 * </ul>
694 */
695 public ClassLoader getClassLoader() {
696 if (this.classLoader != null) {
697 return (this.classLoader);
698 }
699 if (this.useContextClassLoader) {
700 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
701 if (classLoader != null) {
702 return (classLoader);
703 }
704 }
705 return (this.getClass().getClassLoader());
706 }
707
708 /***
709 * Set the class loader to be used for instantiating application objects
710 * when required.
711 *
712 * @param classLoader The new class loader to use, or <code>null</code>
713 * to revert to the standard rules
714 */
715 public void setClassLoader(ClassLoader classLoader) {
716 this.classLoader = classLoader;
717 }
718
719 /***
720 * Return the boolean as to whether the context classloader should be used.
721 */
722 public boolean getUseContextClassLoader() {
723 return useContextClassLoader;
724 }
725
726 /***
727 * Determine whether to use the Context ClassLoader (the one found by
728 * calling <code>Thread.currentThread().getContextClassLoader()</code>)
729 * to resolve/load classes. If not
730 * using Context ClassLoader, then the class-loading defaults to
731 * using the calling-class' ClassLoader.
732 *
733 * @param boolean determines whether to use JellyContext ClassLoader.
734 */
735 public void setUseContextClassLoader(boolean use) {
736 useContextClassLoader = use;
737 }
738
739
740 // Implementation methods
741 //-------------------------------------------------------------------------
742 /***
743 * @return a new relative URL from the given root and with the addition of the
744 * extra relative URI
745 *
746 * @param rootURL is the root context from which the relative URI will be applied
747 * @param relativeURI is the relative URI (without a leading "/")
748 * @throws MalformedURLException if the URL is invalid.
749 */
750 protected URL createRelativeURL(URL rootURL, String relativeURI)
751 throws MalformedURLException {
752 String urlText = null;
753 if (rootURL == null) {
754 String userDir = System.getProperty("user.dir");
755 urlText = "file://" + userDir + relativeURI;
756 }
757 else {
758 urlText = rootURL.toString() + relativeURI;
759 }
760 if ( log.isDebugEnabled() ) {
761 log.debug("Attempting to open url: " + urlText);
762 }
763 return new URL(urlText);
764 }
765
766 /***
767 * Strips off the name of a script to create a new context URL
768 */
769 protected URL getJellyContextURL(URL url) throws MalformedURLException {
770 String text = url.toString();
771 int idx = text.lastIndexOf('/');
772 text = text.substring(0, idx + 1);
773 return new URL(text);
774 }
775
776 /***
777 * Factory method to create a new child of this context
778 */
779 protected JellyContext createChildContext() {
780 return new JellyContext(this);
781 }
782
783 }
This page was automatically generated by Maven