/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.serialization; import java.awt.Color; import java.io.Serializable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpUtils; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.batik.transcoder.Transcoder; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.TranscodingHints; import org.apache.batik.util.ParsedURL; import org.apache.cocoon.Constants; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.components.transcoder.ExtendableTranscoderFactory; import org.apache.cocoon.components.transcoder.TranscoderFactory; import org.apache.cocoon.components.url.ParsedContextURLProtocolHandler; import org.apache.cocoon.components.url.ParsedResourceURLProtocolHandler; import org.apache.cocoon.environment.http.HttpEnvironment; import org.apache.cocoon.sitemap.SitemapModelComponent; import org.apache.cocoon.util.ClassUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.NOPValidity; import org.w3c.dom.Document; /** * A <a href="http://xml.apache.org/batik/">Batik</a> based Serializer for generating PNG/JPEG images * * sitemap parameter: documentURL (by default httprequest.requestURL is used). The documentURI is used by Batik * to select script interpreters. If the URI is invalid script interpretation will fail. * (See batik 1.7 BridgeContext.java line 96-100) * * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a> * @author <a href="mailto:rossb@apache.org">Ross Burton</a> * @version $Id$ */ public class SVGSerializer extends AbstractDOMSerializer implements Serializer, Configurable, CacheableProcessingComponent, Contextualizable, SitemapModelComponent { /** * Get the context */ public void contextualize(Context context) throws ContextException { ParsedContextURLProtocolHandler.setContext( (org.apache.cocoon.environment.Context)context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT)); ParsedURL.registerHandler(new ParsedContextURLProtocolHandler()); ParsedURL.registerHandler(new ParsedResourceURLProtocolHandler()); } /** The current <code>mime-type</code>. */ private String mimetype; /** The current <code>Transcoder</code>. */ Transcoder transcoder; /** The Transcoder Factory to use */ TranscoderFactory factory = ExtendableTranscoderFactory.getTranscoderFactoryImplementation(); /** * Set the configurations for this serializer. */ public void configure(Configuration conf) throws ConfigurationException { this.mimetype = conf.getAttribute("mime-type"); if (getLogger().isDebugEnabled()) { getLogger().debug("mime-type: " + mimetype); } // Using the Transcoder Factory, get the default transcoder // for this MIME type. this.transcoder = factory.createTranscoder(mimetype); // Iterate through the parameters, looking for a transcoder reference Configuration[] parameters = conf.getChildren("parameter"); for (int i = 0; i < parameters.length; i++) { String name = parameters[i].getAttribute("name"); if ("transcoder".equals(name)) { String transcoderName = parameters[i].getAttribute("value"); try { this.transcoder = (Transcoder)ClassUtils.newInstance(transcoderName); } catch (Exception ex) { if (getLogger().isDebugEnabled()) { getLogger().debug("Cannot load class " + transcoderName, ex); } throw new ConfigurationException("Cannot load class " + transcoderName, ex); } } } // Do we have a transcoder yet? if (this.transcoder == null ) { throw new ConfigurationException( "Could not autodetect transcoder for SVGSerializer and " + "no transcoder was specified in the sitemap configuration." ); } // Now run through the other parameters, using them as hints // to the transcoder for (int i = 0; i < parameters.length; i++ ) { String name = parameters[i].getAttribute("name"); // Skip over the parameters we've dealt with. Ensure this // is kept in sync with the above list! if ("transcoder".equals(name)) { continue; } // Now try and get the hints out try { // Turn it into a key name (assume the current Batik style continues! name = ("KEY_" + name).toUpperCase(); // Use reflection to get a reference to the key object TranscodingHints.Key key = (TranscodingHints.Key) (transcoder.getClass().getField(name).get(transcoder)); Object value; String keyType = parameters[i].getAttribute("type", "STRING").toUpperCase(); if ("FLOAT".equals(keyType)) { // Can throw an exception. value = new Float(parameters[i].getAttributeAsFloat("value")); } else if ("INTEGER".equals(keyType)) { // Can throw an exception. value = new Integer(parameters[i].getAttributeAsInteger("value")); } else if ("BOOLEAN".equals(keyType)) { // Can throw an exception. value = BooleanUtils.toBooleanObject(parameters[i].getAttributeAsBoolean("value")); } else if ("COLOR".equals(keyType)) { // Can throw an exception String stringValue = parameters[i].getAttribute("value"); if (stringValue.startsWith("#")) { stringValue = stringValue.substring(1); } value = new Color(Integer.parseInt(stringValue, 16)); } else { // Assume String, and get the value. Allow an empty string. value = parameters[i].getAttribute("value", ""); } if(getLogger().isDebugEnabled()) { getLogger().debug("Adding hint \"" + name + "\" with value \"" + value.toString() + "\""); } transcoder.addTranscodingHint(key, value); } catch (ClassCastException ex) { // This is only thrown from the String keyType... line throw new ConfigurationException("Specified key (" + name + ") is not a valid Batik Transcoder key.", ex); } catch (ConfigurationException ex) { throw new ConfigurationException("Name or value not specified.", ex); } catch (IllegalAccessException ex) { throw new ConfigurationException("Cannot access the key for parameter \"" + name + "\"", ex); } catch (NoSuchFieldException ex) { throw new ConfigurationException("No field available for parameter \"" + name + "\"", ex); } } } /** * Receive DOM Document to transcode. */ public void serialize(Document doc) throws Exception { TranscoderInput transInput = new TranscoderInput(doc); HttpServletRequest req = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); String documentUrl = parameters.getParameter("documentUrl", req == null ? null : HttpUtils.getRequestURL(req).toString()); transInput.setURI(documentUrl); // Buffering is done by the pipeline (See shouldSetContentLength) TranscoderOutput transOutput = new TranscoderOutput(this.output); transcoder.transcode(transInput, transOutput); } /** * Return the MIME type. */ public String getMimeType() { return mimetype; } /** * Generate the unique key. * This key must be unique inside the space of this component. * This method must be invoked before the getValidity() method. * * @return The generated key or <code>0</code> if the component * is currently not cacheable. */ public Serializable getKey() { return "1"; } /** * Generate the validity object. * Before this method can be invoked the getKey() method * must be invoked. * * @return The generated validity object or <code>null</code> if the * component is currently not cacheable. */ public SourceValidity getValidity() { return NOPValidity.SHARED_INSTANCE; } /** * Returns true so the pipeline implementation will buffer generated * output and write content length to the response. * <p>Batik's PNGTranscoder closes the output stream, therefore we * cannot pass the output stream directly to Batik and have to * instruct pipeline to buffer it. If we do not buffer, we would get * an exception when * {@link org.apache.cocoon.Cocoon#process(org.apache.cocoon.environment.Environment)} * tries to close the stream. */ public boolean shouldSetContentLength() { return true; } }