blob: 95982a1f48faba92c29ec6adf4de20e134464225 [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 org.apache.avalon.framework.activity.Disposable;
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.configuration.SAXConfigurationHandler;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.NOPValidity;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* FOP 0.93 (and newer) based serializer.
*
* @version $Id$
*/
public class FOPSerializer extends AbstractSerializer
implements Configurable, CacheableProcessingComponent,
Serviceable, URIResolver, Disposable {
protected SourceResolver resolver;
/**
* Factory to create fop objects
*/
protected FopFactory fopfactory = FopFactory.newInstance();
/**
* The FOP instance.
*/
protected Fop fop;
/**
* The current <code>mime-type</code>.
*/
protected String mimetype;
/**
* Should we set the content length ?
*/
protected boolean setContentLength = true;
/**
* Manager to get URLFactory from.
*/
protected ServiceManager manager;
private Map rendererOptions;
/**
* Set the component manager for this serializer.
*/
public void service(ServiceManager manager) throws ServiceException {
this.manager = manager;
this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
}
/**
* Set the configurations for this serializer.
*/
public void configure(Configuration conf) throws ConfigurationException {
//should the content length be set
this.setContentLength = conf.getChild("set-content-length").getValueAsBoolean(true);
String configUrl = conf.getChild("user-config").getValue(null);
if (configUrl != null) {
Source configSource = null;
SourceResolver resolver = null;
try {
resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
configSource = resolver.resolveURI(configUrl);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loading configuration from " + configSource.getURI());
}
SAXConfigurationHandler configHandler = new SAXConfigurationHandler();
SourceUtil.toSAX(configSource, configHandler);
fopfactory.setUserConfig(configHandler.getConfiguration());
} catch (Exception e) {
getLogger().warn("Cannot load configuration from " + configUrl);
throw new ConfigurationException("Cannot load configuration from " + configUrl, e);
} finally {
if (resolver != null) {
resolver.release(configSource);
manager.release(resolver);
}
}
}
fopfactory.setURIResolver(this);
// Get the mime type.
this.mimetype = conf.getAttribute("mime-type");
Configuration confRenderer = conf.getChild("renderer-config");
if (confRenderer != null) {
Configuration[] parameters = confRenderer.getChildren("parameter");
if (parameters.length > 0) {
rendererOptions = new HashMap();
for (int i = 0; i < parameters.length; i++) {
String name = parameters[i].getAttribute("name");
String value = parameters[i].getAttribute("value");
if (getLogger().isDebugEnabled()) {
getLogger().debug("renderer " + String.valueOf(name) + " = " + String.valueOf(value));
}
}
}
}
}
/**
* Recycle serializer by removing references
*/
public void recycle() {
super.recycle();
this.fop = null;
}
public void dispose() {
if (this.resolver != null) {
this.manager.release(this.resolver);
this.resolver = null;
}
this.manager = null;
}
// -----------------------------------------------------------------
/**
* Return the MIME type.
*/
public String getMimeType() {
return mimetype;
}
/**
* Create the FOP driver
* Set the <code>OutputStream</code> where the XML should be serialized.
* @throws IOException
*/
public void setOutputStream(OutputStream out) throws IOException {
// Give the source resolver to Batik which is used by FOP
//SourceProtocolHandler.setup(this.resolver);
FOUserAgent userAgent = fopfactory.newFOUserAgent();
if (this.rendererOptions != null) {
userAgent.getRendererOptions().putAll(this.rendererOptions);
}
try {
this.fop = fopfactory.newFop(getMimeType(), userAgent, out);
setContentHandler(this.fop.getDefaultHandler());
} catch (FOPException e) {
getLogger().error("FOP setup failed", e);
throw new IOException("Unable to setup fop: " + e.getLocalizedMessage());
}
}
/**
* Generate the unique key.
* This key must be unique inside the space of this component.
* This method must be invoked before the generateValidity() 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 generateKey() 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;
}
/**
* Test if the component wants to set the content length
*/
public boolean shouldSetContentLength() {
return this.setContentLength;
}
//From URIResolver, copied from TraxProcessor
public javax.xml.transform.Source resolve(String href, String base) throws TransformerException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("resolve(href = " + href + ", base = " + base + "); resolver = " + resolver);
}
StreamSource streamSource = null;
Source source = null;
try {
if (base == null || href.indexOf(":") > 1) {
// Null base - href must be an absolute URL
source = resolver.resolveURI(href);
} else if (href.length() == 0) {
// Empty href resolves to base
source = resolver.resolveURI(base);
} else {
// is the base a file or a real m_url
if (!base.startsWith("file:")) {
int lastPathElementPos = base.lastIndexOf('/');
if (lastPathElementPos == -1) {
// this should never occur as the base should
// always be protocol:/....
return null; // we can't resolve this
} else {
source = resolver.resolveURI(base.substring(0, lastPathElementPos) + "/" + href);
}
} else {
File parent = new File(base.substring(5));
File parent2 = new File(parent.getParentFile(), href);
source = resolver.resolveURI(parent2.toURI().toURL().toExternalForm());
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("source = " + source + ", system id = " + source.getURI());
}
streamSource = new StreamSource(new ReleaseSourceInputStream(source.getInputStream(), source, resolver), source.getURI());
} catch (SourceException e) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Failed to resolve " + href + "(base = " + base + "), return null", e);
}
// CZ: To obtain the same behaviour as when the resource is
// transformed by the XSLT Transformer we should return null here.
return null;
} catch (java.net.MalformedURLException mue) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Failed to resolve " + href + "(base = " + base + "), return null", mue);
}
return null;
} catch (IOException ioe) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Failed to resolve " + href + "(base = " + base + "), return null", ioe);
}
return null;
} finally {
// If streamSource is not null, the source should only be released when the input stream
// is not needed anymore.
if (streamSource == null)
resolver.release(source);
}
return streamSource;
}
/**
* An InputStream which releases the Cocoon/Avalon source from which the InputStream
* has been retrieved when the stream is closed.
*/
public static class ReleaseSourceInputStream extends InputStream {
private InputStream delegate;
private Source source;
private SourceResolver sourceResolver;
private ReleaseSourceInputStream(InputStream delegate, Source source, SourceResolver sourceResolver) {
this.delegate = delegate;
this.source = source;
this.sourceResolver = sourceResolver;
}
public void close() throws IOException {
delegate.close();
sourceResolver.release(source);
}
public int read() throws IOException {
return delegate.read();
}
public int read(byte b[]) throws IOException {
return delegate.read(b);
}
public int read(byte b[], int off, int len) throws IOException {
return delegate.read(b, off, len);
}
public long skip(long n) throws IOException {
return delegate.skip(n);
}
public int available() throws IOException {
return delegate.available();
}
public synchronized void mark(int readlimit) {
delegate.mark(readlimit);
}
public synchronized void reset() throws IOException {
delegate.reset();
}
public boolean markSupported() {
return delegate.markSupported();
}
}
}