001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.language.markup;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.configuration.Configurable;
021: import org.apache.avalon.framework.configuration.Configuration;
022: import org.apache.avalon.framework.configuration.ConfigurationException;
023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
024: import org.apache.avalon.framework.parameters.Parameters;
025: import org.apache.avalon.framework.service.ServiceException;
026: import org.apache.avalon.framework.service.ServiceManager;
027: import org.apache.avalon.framework.service.Serviceable;
028:
029: import org.apache.avalon.excalibur.pool.Recyclable;
030: import org.apache.excalibur.source.Source;
031: import org.apache.excalibur.source.SourceException;
032: import org.apache.excalibur.source.SourceResolver;
033:
034: import org.apache.cocoon.ProcessingException;
035: import org.apache.cocoon.xml.AbstractXMLPipe;
036: import org.apache.cocoon.components.language.programming.ProgrammingLanguage;
037: import org.apache.cocoon.components.source.SourceUtil;
038: import org.apache.excalibur.store.Store;
039:
040: import org.xml.sax.Attributes;
041: import org.xml.sax.SAXException;
042:
043: import java.io.IOException;
044: import java.net.MalformedURLException;
045: import java.util.ArrayList;
046: import java.util.HashMap;
047: import java.util.Iterator;
048: import java.util.LinkedList;
049: import java.util.List;
050: import java.util.Map;
051:
052: /**
053: * Base implementation of <code>MarkupLanguage</code>. This class uses
054: * logicsheets as the only means of code generation. Code generation
055: * should be decoupled from this context!!!
056: *
057: * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
058: * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
059: * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
060: * @version CVS $Id: AbstractMarkupLanguage.java 433543 2006-08-22 06:22:54Z crossley $
061: */
062: public abstract class AbstractMarkupLanguage extends AbstractLogEnabled
063: implements MarkupLanguage, Serviceable, Configurable,
064: Recyclable, Disposable {
065: /**
066: * Name "attr-interpolation" of boolean attribute to enable
067: * expression interpolation in attribute values.
068: */
069: public static final String ATTR_INTERPOLATION = "attr-interpolation";
070:
071: /**
072: * Name "text-interpolation" of boolean attribute to enable
073: * expression interpolation inside text nodes.
074: */
075: public static final String TEXT_INTERPOLATION = "text-interpolation";
076:
077: /** The 'file' URL protocol. */
078: private static final String FILE = "file:";
079:
080: /** Prefix for cache keys to avoid name clash with the XSLTProcessor */
081: private static final String CACHE_PREFIX = "logicsheet:";
082:
083: /** This language name */
084: protected String name;
085:
086: /** The supported language table */
087: protected Map languages;
088:
089: /** The code-generation logicsheet cache */
090: protected Store logicsheetCache;
091:
092: /** The markup language's namespace uri */
093: private String uri;
094:
095: /** The markup language's namespace prefix */
096: private String prefix;
097:
098: /** Are attribute expressions to be expanded? */
099: private boolean attrInterpolation;
100:
101: /** Are text expressions to be expanded? */
102: private boolean textInterpolation;
103:
104: /** The service manager */
105: protected ServiceManager manager;
106:
107: /** The URL factory source resolver used to resolve URIs */
108: private SourceResolver resolver;
109:
110: /**
111: * Stores the list of logicsheets required by the currently
112: * loaded program.
113: */
114: private final LinkedList logicSheetList = new LinkedList();
115:
116: /** The default constructor. */
117: public AbstractMarkupLanguage() {
118: // Initialize language table
119: this .languages = new HashMap();
120: }
121:
122: /**
123: * Process additional configuration. Load supported programming
124: * language definitions
125: *
126: * @param conf The language configuration
127: * @exception ConfigurationException If an error occurs loading logichseets
128: */
129: public void configure(Configuration conf)
130: throws ConfigurationException {
131: try {
132: this .name = conf.getAttribute("name");
133:
134: // Cannot use Parameterizable because parameterize() is called
135: // after configure(), and <xsp-language> param's are already
136: // needed for processing logicsheet definitions.
137: Parameters params = Parameters.fromConfiguration(conf);
138: this .uri = params.getParameter("uri");
139: this .prefix = params.getParameter("prefix", null);
140: this .attrInterpolation = params.getParameterAsBoolean(
141: ATTR_INTERPOLATION, false);
142: this .textInterpolation = params.getParameterAsBoolean(
143: TEXT_INTERPOLATION, false);
144:
145: // Set up each target-language
146: Configuration[] l = conf.getChildren("target-language");
147: for (int i = 0; i < l.length; i++) {
148: LanguageDescriptor language = new LanguageDescriptor();
149: language.setName(l[i].getAttribute("name"));
150:
151: // Create & Store the core logicsheet
152: Logicsheet logicsheet = createLogicsheet(l[i], false);
153: language.setLogicsheet(logicsheet.getSystemId());
154:
155: // Set up each built-in logicsheet
156: Configuration[] n = l[i]
157: .getChildren("builtin-logicsheet");
158: for (int j = 0; j < n.length; j++) {
159: // Create & Store the named logicsheets
160: NamedLogicsheet namedLogicsheet = (NamedLogicsheet) createLogicsheet(
161: n[j], true);
162:
163: language.addNamedLogicsheet(namedLogicsheet
164: .getURI(), namedLogicsheet.getPrefix(),
165: namedLogicsheet.getSystemId());
166: }
167:
168: this .languages.put(language.getName(), language);
169: }
170: } catch (Exception e) {
171: getLogger().warn("Configuration Error: " + e.getMessage(),
172: e);
173: throw new ConfigurationException("AbstractMarkupLanguage: "
174: + e.getMessage(), e);
175: }
176: }
177:
178: /**
179: * Abstract out the Logicsheet creation. Handles both Named and regular logicsheets.
180: */
181: private Logicsheet createLogicsheet(Configuration configuration,
182: boolean named) throws Exception {
183: Parameters params = Parameters.fromConfiguration(configuration);
184:
185: Logicsheet logicsheet;
186: if (named) {
187: String location = params.getParameter("href", null);
188: String uri = params.getParameter("uri", null);
189: String prefix = params.getParameter("prefix", null);
190:
191: NamedLogicsheet namedLogicsheet = new NamedLogicsheet(
192: location, manager, resolver, getLogicsheetFilter());
193: namedLogicsheet.setURI(uri);
194: namedLogicsheet.setPrefix(prefix);
195: logicsheet = namedLogicsheet;
196: } else {
197: String location = params.getParameter("core-logicsheet",
198: null);
199: logicsheet = new Logicsheet(location, manager, resolver,
200: getLogicsheetFilter());
201: }
202:
203: logicsheet.enableLogging(getLogger());
204:
205: String logicsheetName = logicsheet.getSystemId();
206: logicsheetCache
207: .store(CACHE_PREFIX + logicsheetName, logicsheet);
208:
209: return logicsheet;
210: }
211:
212: /**
213: * Set the global service manager.
214: * @param manager The sitemap-specified service manager
215: */
216: public void service(ServiceManager manager) throws ServiceException {
217: this .manager = manager;
218:
219: // Initialize logicsheet cache
220: this .logicsheetCache = (Store) manager
221: .lookup(Store.TRANSIENT_STORE);
222:
223: // Initialize the source resolver
224: this .resolver = (SourceResolver) this .manager
225: .lookup(SourceResolver.ROLE);
226: }
227:
228: /**
229: * Recycle this component: clear logic sheet list and dependencies.
230: */
231: public void recycle() {
232: this .logicSheetList.clear();
233: }
234:
235: /**
236: * Release all resources.
237: */
238: public void dispose() {
239: this .manager.release(this .logicsheetCache);
240: this .logicsheetCache = null;
241:
242: this .manager.release(this .resolver);
243: this .resolver = null;
244: this .manager = null;
245: this .languages.clear();
246: }
247:
248: /**
249: * Return the markup language name. Two markup languages are
250: * well-know at the moment: sitemap and xsp.
251: *
252: * @return The language name.
253: */
254: public String getName() {
255: return this .name;
256: }
257:
258: /**
259: * Returns the namespace URI for this language.
260: */
261: public String getURI() {
262: return this .uri;
263: }
264:
265: /**
266: * Returns the namespace prefix for this language.
267: */
268: public String getPrefix() {
269: return this .prefix;
270: }
271:
272: /**
273: * Returns true if expansion of attribute expressions is enabled
274: * for this language.
275: */
276: public boolean hasAttrInterpolation() {
277: return this .attrInterpolation;
278: }
279:
280: /**
281: * Returns true if expansion of expressions inside text nodes is enabled
282: * for this language.
283: */
284: public boolean hasTextInterpolation() {
285: return this .textInterpolation;
286: }
287:
288: /**
289: * Return the source document's encoding. This can be <code>null</code> for
290: * the platform's default encoding. The default implementation returns
291: * <code>null</code>, but derived classes may override it if encoding applies to
292: * their concrete languages.
293: *
294: * FIXME: There should be a way to get the
295: * XML document's encoding as seen by the parser; unfortunately, this
296: * information is not returned by current DOM or SAX parsers...
297: *
298: * @return The document-specified encoding
299: */
300: public String getEncoding() {
301: return null;
302: }
303:
304: /**
305: * Returns a filter that chains on the fly the requested
306: * transformers for source code generation. This method scans the
307: * input SAX events for built-in logicsheet declared as namespace
308: * attribute on the root element. Derived class should overide
309: * this method and the public inner class in order to add more
310: * specif action and to build a more specific transformer chain.
311: *
312: * @param logicsheetMarkupGenerator the logicsheet markup generator
313: * @return XMLFilter the filter that build on the fly the transformer chain
314: */
315: protected TransformerChainBuilderFilter getTransformerChainBuilder(
316: LogicsheetCodeGenerator logicsheetMarkupGenerator) {
317: return new TransformerChainBuilderFilter(
318: logicsheetMarkupGenerator);
319: }
320:
321: /**
322: * Prepare the input source for logicsheet processing and code
323: * generation with a preprocess filter. The return
324: * <code>XMLFilter</code> object is the first filter on the
325: * transformer chain. The default implementation does nothing by
326: * returning a identity filter, but derived classes should (at
327: * least) use the passed programming language to quote
328: * <code>Strings</code>
329: *
330: * @param filename The source filename
331: * @param language The target programming language
332: * @return The preprocess filter
333: */
334: protected AbstractXMLPipe getPreprocessFilter(String filename,
335: AbstractXMLPipe filter, ProgrammingLanguage language) {
336: // No-op
337: return filter;
338: }
339:
340: /**
341: * Add a dependency on an external file to the document for inclusion in
342: * generated code. This is used to populate a list of <code>File</code>'s
343: * tested for change on each invocation; this information is used to assert whether regeneration is necessary.
344: *
345: * @param location The file path of the dependent file
346: * @see AbstractMarkupLanguage
347: * @see org.apache.cocoon.generation.ServerPagesGenerator
348: * @see org.apache.cocoon.generation.AbstractServerPage
349: */
350: protected abstract void addDependency(String location);
351:
352: /**
353: * Generate source code from the input document for the target
354: * <code>ProgrammingLanguage</code>. After preprocessing the input
355: * document, this method applies logicsheets in the following
356: * order:
357: *
358: * <ul>
359: * <li>User-defined logicsheets</li>
360: * <li>Namespace-mapped logicsheets</li>
361: * <li>Language-specific logicsheet</li>
362: * </ul>
363: *
364: * @param source The input source
365: * @param filename The input document's original filename
366: * @param programmingLanguage The target programming language
367: * @return The generated source code
368: * @exception Exception If an error occurs during code generation
369: */
370: public String generateCode(Source source, String filename,
371: ProgrammingLanguage programmingLanguage) throws Exception {
372:
373: String languageName = programmingLanguage.getLanguageName();
374: LanguageDescriptor language = (LanguageDescriptor) this .languages
375: .get(languageName);
376: if (language == null) {
377: throw new IllegalArgumentException(
378: "Unsupported programming language: " + languageName);
379: }
380:
381: // Create code generator
382: LogicsheetCodeGenerator codeGenerator = new LogicsheetCodeGenerator();
383: codeGenerator.enableLogging(getLogger());
384: codeGenerator.initialize();
385: // Set the transformer chain builder filter
386: TransformerChainBuilderFilter tranBuilder = getTransformerChainBuilder(codeGenerator);
387: tranBuilder.setLanguageDescriptor(language);
388:
389: // Get the needed preprocess filter
390: AbstractXMLPipe preprocessor = getPreprocessFilter(filename,
391: tranBuilder, programmingLanguage);
392: return codeGenerator.generateCode(source, preprocessor);
393: }
394:
395: /**
396: * Add logicsheet list to the code generator.
397: * @param codeGenerator The code generator
398: */
399: protected void addLogicsheetsToGenerator(
400: LogicsheetCodeGenerator codeGenerator)
401: throws MalformedURLException, IOException, SAXException,
402: ProcessingException {
403:
404: if (codeGenerator == null) {
405: getLogger().debug(
406: "This should never happen: codeGenerator is null");
407: throw new SAXException("codeGenerator must never be null.");
408: }
409:
410: // Walk backwards and remove duplicates.
411: LinkedList newLogicSheetList = new LinkedList();
412: for (int i = logicSheetList.size() - 1; i >= 0; i--) {
413: Logicsheet logicsheet = (Logicsheet) logicSheetList.get(i);
414: if (newLogicSheetList.indexOf(logicsheet) == -1)
415: newLogicSheetList.addFirst(logicsheet);
416: }
417:
418: // Add the list of logicsheets now.
419: Iterator iterator = newLogicSheetList.iterator();
420: while (iterator.hasNext()) {
421: Logicsheet logicsheet = (Logicsheet) iterator.next();
422: codeGenerator.addLogicsheet(logicsheet);
423: }
424: }
425:
426: /**
427: * Add a logicsheet to the code generator.
428: * @param language Target programming language of the logicsheet
429: * @param logicsheetLocation Location of the logicsheet to be added
430: * @exception MalformedURLException If location is invalid
431: * @exception IOException IO Error
432: * @exception SAXException Logicsheet parse error
433: */
434: protected void addLogicsheetToList(LanguageDescriptor language,
435: String logicsheetLocation) throws IOException,
436: SAXException, ProcessingException {
437: Source inputSource = null;
438: String logicsheetName;
439: try {
440: // Logicsheet is reusable (across multiple XSPs) object,
441: // and it is resolved via urlResolver, and not via per-request
442: // temporary resolver.
443: // Resolve logicsheet location relative to sitemap from where it is used.
444: inputSource = this .resolver.resolveURI(logicsheetLocation);
445: logicsheetName = inputSource.getURI();
446: } catch (SourceException se) {
447: throw SourceUtil.handle(se);
448: } finally {
449: this .resolver.release(inputSource);
450: }
451:
452: // Logicsheets are chained by looking at the namespaces on the xsl:stylesheet
453: // root node. To get at these namespaces, the stylesheet must be parsed.
454: // Stylesheets are cached that we have only one chance to fill the namespaces.
455: // To avoid a race condition, we have to lock the critical section.
456: // For maximum concurrency we lock the cache, store if necessary the new,
457: // unparsed logicsheet, and then lock the logicsheet for the long-running
458: // parse operation.
459:
460: Logicsheet logicsheet;
461: synchronized (logicsheetCache) {
462: String cacheKey = CACHE_PREFIX + logicsheetName;
463: logicsheet = (Logicsheet) logicsheetCache.get(cacheKey);
464: if (logicsheet == null) {
465: // Resolver (local) could not be used as it is temporary
466: // (per-request) object, yet Logicsheet is being cached and reused
467: // across multiple requests. "Global" url-factory-based resolver
468: // passed to the Logicsheet.
469: logicsheet = new Logicsheet(logicsheetName, manager,
470: resolver, getLogicsheetFilter());
471: logicsheetCache.store(cacheKey, logicsheet);
472: }
473: }
474:
475: synchronized (logicsheet) {
476: Map namespaces = logicsheet.getNamespaceURIs();
477: if (namespaces == null)
478: logicsheet.fillNamespaceURIs();
479: }
480:
481: if (getLogger().isDebugEnabled()) {
482: getLogger().debug(
483: "addLogicsheetToList: " + "name: " + logicsheetName
484: + ", location: " + logicsheetLocation
485: + ", instance: " + logicsheet);
486: }
487:
488: if (logicsheetName.startsWith(FILE)) {
489: String filename = logicsheetName.substring(FILE.length());
490: addDependency(filename);
491: getLogger().debug(
492: "addLogicsheetToList: "
493: + "adding dependency on file " + filename);
494: }
495:
496: logicSheetList.add(logicsheet);
497:
498: Map namespaces = logicsheet.getNamespaceURIs();
499: if (!logicsheetLocation.equals(language.getLogicsheet())) {
500: if (namespaces.size() > 0) {
501: Iterator iter = namespaces.keySet().iterator();
502: while (iter.hasNext()) {
503: String namespace = (String) iter.next();
504: String namedLogicsheetName = language
505: .getNamedLogicsheetByURI(namespace);
506: if (namedLogicsheetName != null
507: && !logicsheetLocation
508: .equals(namedLogicsheetName)) {
509: getLogger().debug(
510: "Adding embedded logic sheet for "
511: + namespace + ": "
512: + namedLogicsheetName);
513: // Add embedded logic sheets too.
514: addLogicsheetToList(language,
515: namedLogicsheetName);
516: }
517: }
518: }
519: }
520: }
521:
522: /**
523: * Return the optional filter to prepocess logicsheets.
524: */
525: protected LogicsheetFilter getLogicsheetFilter() {
526: return new LogicsheetFilter();
527: }
528:
529: //
530: // Inner classes
531: //
532:
533: /** This class holds transient information about a target programming language. */
534: protected static class LanguageDescriptor {
535: /** The progamming language name */
536: protected String name;
537:
538: /** The progamming language core logicsheet */
539: protected String logicsheet;
540:
541: /** The list of built-in logicsheets defined for this target language */
542: protected HashMap namedLogicsheets;
543:
544: /** The default constructor */
545: protected LanguageDescriptor() {
546: this .namedLogicsheets = new HashMap();
547: }
548:
549: /**
550: * Set the programming language's name
551: * @param name The programming language's name
552: */
553: protected void setName(String name) {
554: this .name = name;
555: }
556:
557: /**
558: * Return the programming language's name
559: * @return The programming language's name
560: */
561: protected String getName() {
562: return this .name;
563: }
564:
565: /**
566: * Set the programming language's core logichseet location
567: * @param logicsheet The programming language's core logichseet location
568: */
569: protected void setLogicsheet(String logicsheet) {
570: this .logicsheet = logicsheet;
571: }
572:
573: /**
574: * Return the programming language's core logichseet location
575: * @return The programming language's core logichseet location
576: */
577: protected String getLogicsheet() {
578: return this .logicsheet;
579: }
580:
581: /**
582: * Add a namespace-mapped logicsheet to this language
583: * @param prefix The logichseet's namespace prefix
584: * @param uri The logichseet's namespace uri
585: * @param namedLogicsheet The logichseet's location
586: */
587: protected void addNamedLogicsheet(String uri, String prefix,
588: String namedLogicsheet) {
589: this .namedLogicsheets.put(uri, namedLogicsheet);
590: }
591:
592: /**
593: * Return a namespace-mapped logicsheet given its uri
594: * @return The namespace-mapped logicsheet
595: */
596: protected String getNamedLogicsheetByURI(String uri) {
597: return (String) this .namedLogicsheets.get(uri);
598: }
599: }
600:
601: /**
602: * An XMLFilter that build the chain of transformers on the fly.
603: * Each time a stylesheet is found, a call to the code generator is done
604: * to add the new transformer at the end of the current transformer chain.
605: */
606: public class TransformerChainBuilderFilter extends AbstractXMLPipe {
607: /** The markup generator */
608: protected LogicsheetCodeGenerator logicsheetMarkupGenerator;
609:
610: /** the language description */
611: protected LanguageDescriptor language;
612:
613: private boolean isRootElem;
614: private List startPrefixes;
615:
616: /**
617: * the constructor depends on the code generator
618: * @param logicsheetMarkupGenerator The code generator
619: */
620: protected TransformerChainBuilderFilter(
621: LogicsheetCodeGenerator logicsheetMarkupGenerator) {
622: this .logicsheetMarkupGenerator = logicsheetMarkupGenerator;
623: }
624:
625: /**
626: * This method should be called prior to receiving any SAX event.
627: * Indeed the language information is needed to get the core stylesheet.
628: * @param language the language in used
629: */
630: protected void setLanguageDescriptor(LanguageDescriptor language) {
631: this .language = language;
632: }
633:
634: /** @see org.xml.sax.ContentHandler */
635: public void startDocument() throws SAXException {
636: isRootElem = true;
637: startPrefixes = new ArrayList();
638: }
639:
640: /** @see org.xml.sax.ContentHandler */
641: public void startPrefixMapping(String prefix, String uri)
642: throws SAXException {
643: if (!isRootElem) {
644: super .startPrefixMapping(prefix, uri);
645: } else {
646: // Cache the prefix mapping
647: String[] prefixNamingArray = new String[2];
648: prefixNamingArray[0] = prefix;
649: prefixNamingArray[1] = uri;
650: this .startPrefixes.add(prefixNamingArray);
651: }
652: }
653:
654: /** @see org.xml.sax.ContentHandler */
655: public void startElement(String namespaceURI, String localName,
656: String qName, Attributes atts) throws SAXException {
657: if (isRootElem) {
658: isRootElem = false;
659: try {
660: // Add namespace-mapped logicsheets
661: int prefixesCount = this .startPrefixes.size();
662: for (int i = 0; i < prefixesCount; i++) {
663: String[] prefixNamingArray = (String[]) this .startPrefixes
664: .get(i);
665: String namedLogicsheetName = this .language
666: .getNamedLogicsheetByURI(prefixNamingArray[1]);
667: if (namedLogicsheetName != null) {
668: AbstractMarkupLanguage.this
669: .addLogicsheetToList(language,
670: namedLogicsheetName);
671: }
672: }
673:
674: // Add the language stylesheet (Always the last one)
675: AbstractMarkupLanguage.this .addLogicsheetToList(
676: language, this .language.getLogicsheet());
677: AbstractMarkupLanguage.this
678: .addLogicsheetsToGenerator(this .logicsheetMarkupGenerator);
679: } catch (ProcessingException pe) {
680: throw new SAXException(pe);
681: } catch (IOException ioe) {
682: throw new SAXException(ioe);
683: }
684:
685: // All stylesheet have been configured and correctly setup.
686: // Starts firing SAX events, especially the startDocument event,
687: // and the cached prefixNaming.
688: super .startDocument();
689: int prefixesCount = this .startPrefixes.size();
690: for (int i = 0; i < prefixesCount; i++) {
691: String[] prefixNamingArray = (String[]) this .startPrefixes
692: .get(i);
693: super .startPrefixMapping(prefixNamingArray[0],
694: prefixNamingArray[1]);
695: }
696: }
697: // Call super method
698: super.startElement(namespaceURI, localName, qName, atts);
699: }
700: }
701: }
|