blob: 39a688654c60119dbd8d5f9a9ae9c0f1056c4e9e [file] [log] [blame]
/*
* 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;
}
}