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.transformation;
018:
019: import java.io.IOException;
020: import java.io.Serializable;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.apache.avalon.framework.activity.Disposable;
026: import org.apache.avalon.framework.configuration.Configurable;
027: import org.apache.avalon.framework.configuration.Configuration;
028: import org.apache.avalon.framework.configuration.ConfigurationException;
029: import org.apache.avalon.framework.parameters.Parameters;
030: import org.apache.avalon.framework.service.ServiceException;
031: import org.apache.avalon.framework.service.ServiceManager;
032: import org.apache.avalon.framework.service.Serviceable;
033:
034: import org.apache.cocoon.ProcessingException;
035: import org.apache.cocoon.caching.CacheableProcessingComponent;
036: import org.apache.cocoon.components.sax.XMLSerializer;
037: import org.apache.cocoon.environment.ObjectModelHelper;
038: import org.apache.cocoon.environment.SourceResolver;
039: import org.apache.cocoon.util.HashUtil;
040:
041: import org.apache.excalibur.source.SourceValidity;
042: import org.apache.excalibur.source.impl.validity.NOPValidity;
043: import org.apache.excalibur.store.Store;
044:
045: import org.xml.sax.Attributes;
046: import org.xml.sax.SAXException;
047: import org.xml.sax.helpers.AttributesImpl;
048:
049: /**
050: * The transformation half of the FragmentExtractor.
051: *
052: * This transformer recieves an incoming stream of xml and replaces
053: * fragments with an fragment extractor locator pointing to the fragments.
054: *
055: * The extracted fragments are identified by their element name and namespace URI.
056: * The default is to extract SVG images ("svg" elements in namespace
057: * "http://www.w3.org/2000/svg"), but this can be overriden in the configuration:
058: * <pre>
059: * <extract-uri>http://my/namespace/uri</extract-uri>
060: * <extract-element>my-element</extract-element>
061: * </pre>
062: *
063: * Fragment extractor locator format is following:
064: * <pre>
065: * <fe:fragment xmlns:fe="http://apache.org/cocoon/fragmentextractor/2.0" fragment-id="..."/>
066: * </pre>
067: *
068: * @author <a href="mailto:paul@luminas.co.uk">Paul Russell</a>
069: * @version CVS $Id: FragmentExtractorTransformer.java 433543 2006-08-22 06:22:54Z crossley $
070: */
071: public class FragmentExtractorTransformer extends AbstractTransformer
072: implements CacheableProcessingComponent, Configurable,
073: Serviceable, Disposable {
074:
075: public static final String FE_URI = "http://apache.org/cocoon/fragmentextractor/2.0";
076:
077: private static final String EXTRACT_URI_NAME = "extract-uri";
078: private static final String EXTRACT_ELEMENT_NAME = "extract-element";
079:
080: private static final String EXTRACT_URI = "http://www.w3.org/2000/svg";
081: private static final String EXTRACT_ELEMENT = "svg";
082:
083: private String extractURI;
084: private String extractElement;
085:
086: /** The ServiceManager instance */
087: protected ServiceManager manager;
088:
089: private XMLSerializer serializer;
090:
091: private Map prefixMap;
092:
093: private int extractLevel;
094:
095: private int fragmentID;
096:
097: private String requestURI;
098:
099: /**
100: * Configure this transformer.
101: */
102: public void configure(Configuration conf)
103: throws ConfigurationException {
104: this .extractURI = conf.getChild(EXTRACT_URI_NAME).getValue(
105: EXTRACT_URI);
106: this .extractElement = conf.getChild(EXTRACT_ELEMENT_NAME)
107: .getValue(EXTRACT_ELEMENT);
108: if (getLogger().isDebugEnabled()) {
109: getLogger().debug("Extraction URI is " + this .extractURI);
110: getLogger().debug(
111: "Extraction element is " + this .extractElement);
112: }
113: }
114:
115: /**
116: * Set the current <code>ServiceManager</code> instance used by this
117: * <code>Serviceable</code>.
118: */
119: public void service(ServiceManager manager) throws ServiceException {
120: this .manager = manager;
121: }
122:
123: /**
124: * Recycle this component
125: */
126: public void recycle() {
127: if (this .manager != null) {
128: this .manager.release(serializer);
129: this .serializer = null;
130: }
131: super .recycle();
132: }
133:
134: /**
135: * Release all resources.
136: */
137: public void dispose() {
138: recycle();
139: this .manager = null;
140: }
141:
142: /**
143: * Setup the transformer.
144: */
145: public void setup(SourceResolver resolver, Map objectModel,
146: String src, Parameters parameters)
147: throws ProcessingException, SAXException, IOException {
148: extractLevel = 0;
149: fragmentID = 0;
150: prefixMap = new HashMap();
151:
152: this .requestURI = ObjectModelHelper.getRequest(objectModel)
153: .getSitemapURI();
154: }
155:
156: /**
157: * Generate the unique key.
158: * This key must be unique inside the space of this component.
159: *
160: * @return "1"
161: */
162: public Serializable getKey() {
163: return "1";
164: }
165:
166: /**
167: * Generate the validity object.
168: *
169: * @return NOPValidity object
170: * - if the input is valid the output is valid as well.
171: */
172: public SourceValidity getValidity() {
173: return NOPValidity.SHARED_INSTANCE;
174: }
175:
176: /**
177: * Begin the scope of a prefix-URI Namespace mapping.
178: *
179: * @param prefix The Namespace prefix being declared.
180: * @param uri The Namespace URI the prefix is mapped to.
181: */
182: public void startPrefixMapping(String prefix, String uri)
183: throws SAXException {
184: if (extractLevel == 0) {
185: super .startPrefixMapping(prefix, uri);
186: prefixMap.put(prefix, uri);
187: } else {
188: this .serializer.startPrefixMapping(prefix, uri);
189: }
190: }
191:
192: /**
193: * End the scope of a prefix-URI mapping.
194: *
195: * @param prefix The prefix that was being mapping.
196: */
197: public void endPrefixMapping(String prefix) throws SAXException {
198: if (extractLevel == 0) {
199: super .endPrefixMapping(prefix);
200: prefixMap.remove(prefix);
201: } else {
202: this .serializer.endPrefixMapping(prefix);
203: }
204: }
205:
206: /**
207: * Receive notification of the beginning of an element.
208: *
209: * @param uri The Namespace URI, or the empty string if the element has no
210: * Namespace URI or if Namespace
211: * processing is not being performed.
212: * @param loc The local name (without prefix), or the empty string if
213: * Namespace processing is not being performed.
214: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
215: * raw names are not available.
216: * @param a The attributes attached to the element. If there are no
217: * attributes, it shall be an empty Attributes object.
218: */
219: public void startElement(String uri, String loc, String raw,
220: Attributes a) throws SAXException {
221: if (uri == null)
222: uri = "";
223: if (this .extractURI.equals(uri)
224: && this .extractElement.equals(loc)) {
225: extractLevel++;
226: fragmentID++;
227: if (getLogger().isDebugEnabled()) {
228: getLogger().debug(
229: "extractLevel now " + extractLevel + ".");
230: }
231:
232: try {
233: this .serializer = (XMLSerializer) this .manager
234: .lookup(XMLSerializer.ROLE);
235: } catch (ServiceException se) {
236: throw new SAXException(
237: "Could not lookup for XMLSerializer.", se);
238: }
239:
240: // Start the DOM document
241: this .serializer.startDocument();
242:
243: Iterator itt = prefixMap.entrySet().iterator();
244: while (itt.hasNext()) {
245: Map.Entry entry = (Map.Entry) itt.next();
246: this .serializer.startPrefixMapping((String) entry
247: .getKey(), (String) entry.getValue());
248: }
249: }
250:
251: if (extractLevel == 0) {
252: super .startElement(uri, loc, raw, a);
253: } else {
254: this .serializer.startElement(uri, loc, raw, a);
255: }
256: }
257:
258: /**
259: * Receive notification of the end of an element.
260: *
261: * @param uri The Namespace URI, or the empty string if the element has no
262: * Namespace URI or if Namespace
263: * processing is not being performed.
264: * @param loc The local name (without prefix), or the empty string if
265: * Namespace processing is not being performed.
266: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
267: * raw names are not available.
268: */
269: public void endElement(String uri, String loc, String raw)
270: throws SAXException {
271: if (extractLevel == 0) {
272: super .endElement(uri, loc, raw);
273: } else {
274: this .serializer.endElement(uri, loc, raw);
275: if (uri == null)
276: uri = "";
277: if (this .extractURI.equals(uri)
278: && this .extractElement.equals(loc)) {
279: extractLevel--;
280: if (getLogger().isDebugEnabled()) {
281: getLogger().debug(
282: "extractLevel now " + extractLevel + ".");
283: }
284:
285: if (extractLevel == 0) {
286: // finish building the fragment. remove existing prefix mappings.
287: Iterator itt = prefixMap.entrySet().iterator();
288: while (itt.hasNext()) {
289: Map.Entry entry = (Map.Entry) itt.next();
290: this .serializer.endPrefixMapping((String) entry
291: .getKey());
292: }
293: this .serializer.endDocument();
294:
295: Store store = null;
296: String id = Long.toHexString((hashCode() ^ HashUtil
297: .hash(requestURI))
298: + fragmentID);
299: try {
300: store = (Store) this .manager
301: .lookup(Store.TRANSIENT_STORE);
302: store.store(id, this .serializer
303: .getSAXFragment());
304: } catch (ServiceException se) {
305: throw new SAXException(
306: "Could not lookup for transient store.",
307: se);
308: } catch (IOException ioe) {
309: throw new SAXException(
310: "Could not store fragment.", ioe);
311: } finally {
312: this .manager.release(store);
313: this .manager.release(this .serializer);
314: this .serializer = null;
315: }
316:
317: if (getLogger().isDebugEnabled()) {
318: getLogger()
319: .debug("Stored document " + id + ".");
320: }
321:
322: // Insert ref.
323: super .startPrefixMapping("fe", FE_URI);
324: AttributesImpl atts = new AttributesImpl();
325: atts.addAttribute("", "fragment-id", "fragment-id",
326: "CDATA", id);
327: super .startElement(FE_URI, "fragment",
328: "fe:fragment", atts);
329: super .endElement(FE_URI, "fragment", "fe:fragment");
330: super .endPrefixMapping("fe");
331: }
332: }
333: }
334: }
335:
336: /**
337: * Receive notification of character data.
338: *
339: * @param c The characters from the XML document.
340: * @param start The start position in the array.
341: * @param len The number of characters to read from the array.
342: */
343: public void characters(char c[], int start, int len)
344: throws SAXException {
345: if (extractLevel == 0) {
346: super .characters(c, start, len);
347: } else {
348: this .serializer.characters(c, start, len);
349: }
350: }
351:
352: /**
353: * Receive notification of ignorable whitespace in element content.
354: *
355: * @param c The characters from the XML document.
356: * @param start The start position in the array.
357: * @param len The number of characters to read from the array.
358: */
359: public void ignorableWhitespace(char c[], int start, int len)
360: throws SAXException {
361: if (extractLevel == 0) {
362: super .ignorableWhitespace(c, start, len);
363: } else {
364: this .serializer.ignorableWhitespace(c, start, len);
365: }
366: }
367:
368: /**
369: * Receive notification of a processing instruction.
370: *
371: * @param target The processing instruction target.
372: * @param data The processing instruction data, or null if none was
373: * supplied.
374: */
375: public void processingInstruction(String target, String data)
376: throws SAXException {
377: if (extractLevel == 0) {
378: super .processingInstruction(target, data);
379: } else {
380: this .serializer.processingInstruction(target, data);
381: }
382: }
383:
384: /**
385: * Receive notification of a skipped entity.
386: *
387: * @param name The name of the skipped entity. If it is a parameter
388: * entity, the name will begin with '%'.
389: */
390: public void skippedEntity(String name) throws SAXException {
391: if (extractLevel == 0) {
392: super .skippedEntity(name);
393: } else {
394: this .serializer.skippedEntity(name);
395: }
396: }
397:
398: /**
399: * Report the start of DTD declarations, if any.
400: *
401: * @param name The document type name.
402: * @param publicId The declared public identifier for the external DTD
403: * subset, or null if none was declared.
404: * @param systemId The declared system identifier for the external DTD
405: * subset, or null if none was declared.
406: */
407: public void startDTD(String name, String publicId, String systemId)
408: throws SAXException {
409: if (extractLevel == 0) {
410: super .startDTD(name, publicId, systemId);
411: } else {
412: throw new SAXException(
413: "Recieved startDTD after beginning fragment extraction process.");
414: }
415: }
416:
417: /**
418: * Report the end of DTD declarations.
419: */
420: public void endDTD() throws SAXException {
421: if (extractLevel == 0) {
422: super .endDTD();
423: } else {
424: throw new SAXException(
425: "Recieved endDTD after beginning fragment extraction process.");
426: }
427: }
428:
429: /**
430: * Report the beginning of an entity.
431: *
432: * @param name The name of the entity. If it is a parameter entity, the
433: * name will begin with '%'.
434: */
435: public void startEntity(String name) throws SAXException {
436: if (extractLevel == 0) {
437: super .startEntity(name);
438: } else {
439: this .serializer.startEntity(name);
440: }
441: }
442:
443: /**
444: * Report the end of an entity.
445: *
446: * @param name The name of the entity that is ending.
447: */
448: public void endEntity(String name) throws SAXException {
449: if (extractLevel == 0) {
450: super .endEntity(name);
451: } else {
452: this .serializer.endEntity(name);
453: }
454: }
455:
456: /**
457: * Report the start of a CDATA section.
458: */
459: public void startCDATA() throws SAXException {
460: if (extractLevel == 0) {
461: super .startCDATA();
462: } else {
463: this .serializer.startCDATA();
464: }
465: }
466:
467: /**
468: * Report the end of a CDATA section.
469: */
470: public void endCDATA() throws SAXException {
471: if (extractLevel == 0) {
472: super .endCDATA();
473: } else {
474: this .serializer.endCDATA();
475: }
476: }
477:
478: /**
479: * Report an XML comment anywhere in the document.
480: *
481: * @param ch An array holding the characters in the comment.
482: * @param start The starting position in the array.
483: * @param len The number of characters to use from the array.
484: */
485: public void comment(char ch[], int start, int len)
486: throws SAXException {
487: if (extractLevel == 0) {
488: super.comment(ch, start, len);
489: } else {
490: this.serializer.comment(ch, start, len);
491: }
492: }
493: }
|