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.serialization;
018:
019: import java.awt.Color;
020: import java.io.OutputStream;
021: import java.io.Serializable;
022:
023: import org.apache.avalon.framework.configuration.Configurable;
024: import org.apache.avalon.framework.configuration.Configuration;
025: import org.apache.avalon.framework.configuration.ConfigurationException;
026: import org.apache.avalon.framework.context.Context;
027: import org.apache.avalon.framework.context.ContextException;
028: import org.apache.avalon.framework.context.Contextualizable;
029: import org.apache.batik.transcoder.Transcoder;
030: import org.apache.batik.transcoder.TranscoderInput;
031: import org.apache.batik.transcoder.TranscoderOutput;
032: import org.apache.batik.transcoder.TranscodingHints;
033: import org.apache.batik.transcoder.TranscoderException;
034: import org.apache.batik.util.ParsedURL;
035: import org.apache.cocoon.Constants;
036: import org.apache.cocoon.caching.CacheableProcessingComponent;
037: import org.apache.cocoon.components.transcoder.ExtendableTranscoderFactory;
038: import org.apache.cocoon.components.transcoder.TranscoderFactory;
039: import org.apache.cocoon.components.url.ParsedContextURLProtocolHandler;
040: import org.apache.cocoon.components.url.ParsedResourceURLProtocolHandler;
041: import org.apache.cocoon.util.ClassUtils;
042: import org.apache.cocoon.xml.dom.SVGBuilder;
043: import org.apache.commons.lang.BooleanUtils;
044: import org.apache.excalibur.source.SourceValidity;
045: import org.apache.excalibur.source.impl.validity.NOPValidity;
046: import org.w3c.dom.Document;
047: import org.xml.sax.SAXException;
048:
049: /**
050: * A <a href="http://xml.apache.org/batik/">Batik</a> based Serializer for generating PNG/JPEG images
051: *
052: * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
053: * @author <a href="mailto:rossb@apache.org">Ross Burton</a>
054: * @version $Id: SVGSerializer.java 433543 2006-08-22 06:22:54Z crossley $
055: */
056: public class SVGSerializer extends SVGBuilder implements Serializer,
057: Configurable, CacheableProcessingComponent, Contextualizable {
058:
059: /**
060: * Get the context
061: */
062: public void contextualize(Context context) throws ContextException {
063: ParsedContextURLProtocolHandler
064: .setContext((org.apache.cocoon.environment.Context) context
065: .get(Constants.CONTEXT_ENVIRONMENT_CONTEXT));
066: ParsedURL
067: .registerHandler(new ParsedContextURLProtocolHandler());
068: ParsedURL
069: .registerHandler(new ParsedResourceURLProtocolHandler());
070: }
071:
072: /** The current <code>OutputStream</code>. */
073: private OutputStream output;
074:
075: /** The current <code>mime-type</code>. */
076: private String mimetype;
077:
078: /** The current <code>Transcoder</code>. */
079: Transcoder transcoder;
080:
081: /** The Transcoder Factory to use */
082: TranscoderFactory factory = ExtendableTranscoderFactory
083: .getTranscoderFactoryImplementation();
084:
085: // private ServiceManager manager;
086:
087: // private SourceResolver resolver;
088:
089: /**
090: * Set the <code>OutputStream</code> where the XML should be serialized.
091: */
092: public void setOutputStream(OutputStream out) {
093: this .output = out;
094:
095: // Give the source resolver to Batik
096: //SourceProtocolHandler.setup(this.resolver);
097: }
098:
099: /* public void service(ServiceManager manager) throws ServiceException {
100: this.manager = manager;
101: this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
102: }
103:
104: public void dispose() {
105: this.manager.release(this.resolver);
106: }
107: */
108: /**
109: * Set the configurations for this serializer.
110: */
111: public void configure(Configuration conf)
112: throws ConfigurationException {
113: this .mimetype = conf.getAttribute("mime-type");
114: if (getLogger().isDebugEnabled()) {
115: getLogger().debug("mime-type: " + mimetype);
116: }
117:
118: // Using the Transcoder Factory, get the default transcoder
119: // for this MIME type.
120: this .transcoder = factory.createTranscoder(mimetype);
121:
122: // Iterate through the parameters, looking for a transcoder reference
123: Configuration[] parameters = conf.getChildren("parameter");
124: for (int i = 0; i < parameters.length; i++) {
125: String name = parameters[i].getAttribute("name");
126: if ("transcoder".equals(name)) {
127: String transcoderName = parameters[i]
128: .getAttribute("value");
129: try {
130: this .transcoder = (Transcoder) ClassUtils
131: .newInstance(transcoderName);
132: } catch (Exception ex) {
133: if (getLogger().isDebugEnabled()) {
134: getLogger().debug(
135: "Cannot load class " + transcoderName,
136: ex);
137: }
138: throw new ConfigurationException(
139: "Cannot load class " + transcoderName, ex);
140: }
141: }
142: }
143: // Do we have a transcoder yet?
144: if (this .transcoder == null) {
145: throw new ConfigurationException(
146: "Could not autodetect transcoder for SVGSerializer and "
147: + "no transcoder was specified in the sitemap configuration.");
148: }
149:
150: // Now run through the other parameters, using them as hints
151: // to the transcoder
152: for (int i = 0; i < parameters.length; i++) {
153: String name = parameters[i].getAttribute("name");
154: // Skip over the parameters we've dealt with. Ensure this
155: // is kept in sync with the above list!
156: if ("transcoder".equals(name)) {
157: continue;
158: }
159:
160: // Now try and get the hints out
161: try {
162: // Turn it into a key name (assume the current Batik style continues!
163: name = ("KEY_" + name).toUpperCase();
164: // Use reflection to get a reference to the key object
165: TranscodingHints.Key key = (TranscodingHints.Key) (transcoder
166: .getClass().getField(name).get(transcoder));
167: Object value;
168: String keyType = parameters[i].getAttribute("type",
169: "STRING").toUpperCase();
170: if ("FLOAT".equals(keyType)) {
171: // Can throw an exception.
172: value = new Float(parameters[i]
173: .getAttributeAsFloat("value"));
174: } else if ("INTEGER".equals(keyType)) {
175: // Can throw an exception.
176: value = new Integer(parameters[i]
177: .getAttributeAsInteger("value"));
178: } else if ("BOOLEAN".equals(keyType)) {
179: // Can throw an exception.
180: value = BooleanUtils.toBooleanObject(parameters[i]
181: .getAttributeAsBoolean("value"));
182: } else if ("COLOR".equals(keyType)) {
183: // Can throw an exception
184: String stringValue = parameters[i]
185: .getAttribute("value");
186: if (stringValue.startsWith("#")) {
187: stringValue = stringValue.substring(1);
188: }
189: value = new Color(Integer.parseInt(stringValue, 16));
190: } else {
191: // Assume String, and get the value. Allow an empty string.
192: value = parameters[i].getAttribute("value", "");
193: }
194: if (getLogger().isDebugEnabled()) {
195: getLogger().debug(
196: "Adding hint \"" + name
197: + "\" with value \""
198: + value.toString() + "\"");
199: }
200: transcoder.addTranscodingHint(key, value);
201: } catch (ClassCastException ex) {
202: // This is only thrown from the String keyType... line
203: throw new ConfigurationException("Specified key ("
204: + name
205: + ") is not a valid Batik Transcoder key.", ex);
206: } catch (ConfigurationException ex) {
207: throw new ConfigurationException(
208: "Name or value not specified.", ex);
209: } catch (IllegalAccessException ex) {
210: throw new ConfigurationException(
211: "Cannot access the key for parameter \"" + name
212: + "\"", ex);
213: } catch (NoSuchFieldException ex) {
214: throw new ConfigurationException(
215: "No field available for parameter \"" + name
216: + "\"", ex);
217: }
218: }
219: }
220:
221: /**
222: * Receive notification of a successfully completed DOM tree generation.
223: */
224: public void notify(Document doc) throws SAXException {
225:
226: try {
227: TranscoderInput transInput = new TranscoderInput(doc);
228:
229: // Buffering is done by the pipeline (See shouldSetContentLength)
230: TranscoderOutput transOutput = new TranscoderOutput(
231: this .output);
232: transcoder.transcode(transInput, transOutput);
233: } catch (TranscoderException ex) {
234: if (ex.getException() != null) {
235: if (getLogger().isDebugEnabled()) {
236: getLogger()
237: .debug(
238: "Got transcoder exception writing image, rethrowing nested exception",
239: ex);
240: }
241: throw new SAXException("Exception writing image", ex
242: .getException());
243: }
244:
245: if (getLogger().isDebugEnabled()) {
246: getLogger()
247: .debug(
248: "Got transcoder exception writing image, rethrowing",
249: ex);
250: }
251: throw new SAXException("Exception writing image", ex);
252: } catch (Exception ex) {
253: if (getLogger().isDebugEnabled()) {
254: getLogger().debug(
255: "Got exception writing image, rethrowing", ex);
256: }
257: throw new SAXException("Exception writing image", ex);
258: }
259: }
260:
261: /**
262: * Return the MIME type.
263: */
264: public String getMimeType() {
265: return mimetype;
266: }
267:
268: /**
269: * Generate the unique key.
270: * This key must be unique inside the space of this component.
271: * This method must be invoked before the getValidity() method.
272: *
273: * @return The generated key or <code>0</code> if the component
274: * is currently not cacheable.
275: */
276: public Serializable getKey() {
277: return "1";
278: }
279:
280: /**
281: * Generate the validity object.
282: * Before this method can be invoked the getKey() method
283: * must be invoked.
284: *
285: * @return The generated validity object or <code>null</code> if the
286: * component is currently not cacheable.
287: */
288: public SourceValidity getValidity() {
289: return NOPValidity.SHARED_INSTANCE;
290: }
291:
292: /**
293: * Returns true so the pipeline implementation will buffer generated
294: * output and write content length to the response.
295: * <p>Batik's PNGTranscoder closes the output stream, therefore we
296: * cannot pass the output stream directly to Batik and have to
297: * instruct pipeline to buffer it. If we do not buffer, we would get
298: * an exception when
299: * {@link org.apache.cocoon.Cocoon#process(org.apache.cocoon.environment.Environment)}
300: * tries to close the stream.
301: */
302: public boolean shouldSetContentLength() {
303: return true;
304: }
305: }
|