/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.processor.serializer.legacy;
import org.apache.log4j.Logger;
import org.orbeon.dom.Document;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.externalcontext.ExternalContext;
import org.orbeon.oxf.pipeline.api.*;
import org.orbeon.oxf.processor.*;
import org.orbeon.oxf.processor.serializer.CachedSerializer;
import org.orbeon.oxf.processor.serializer.store.ResultStore;
import org.orbeon.oxf.processor.serializer.store.ResultStoreOutputStream;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.oxf.xml.XPathUtils;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
/**
* NOTE: This is the deprecated legacy File Serializer. Do not use. Use the new File Serializer in
* the parent package instead, in combination with the XML and other converters.
*/
public class FileSerializer extends ProcessorImpl {
private static Logger logger = LoggerFactory.createLogger(FileSerializer.class);
public static final String FILE_SERIALIZER_CONFIG_NAMESPACE_URI = "http://orbeon.org/oxf/xml/file-serializer-config";
private static final String XML_CONTENT_TYPE = "text/xml";
private static final String HTML_CONTENT_TYPE = "text/html";
private static final String TEXT_CONTENT_TYPE = "text/plain";
private static final String DEFAULT_CONTENT_TYPE = XML_CONTENT_TYPE;
private static final String XML_METHOD = "xml";
private static final String HTML_METHOD = "html";
private static final String TEXT_METHOD = "text";
public static final String DEFAULT_XML_VERSION = "1.0";
public static final String DEFAULT_ENCODING = TransformerUtils.DEFAULT_OUTPUT_ENCODING;
public static final boolean DEFAULT_INDENT = true;
public static final int DEFAULT_INDENT_AMOUNT = 1;
public static final String DIRECTORY_PROPERTY = "directory";
public FileSerializer() {
addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, FILE_SERIALIZER_CONFIG_NAMESPACE_URI));
addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA));
}
private static class Config {
private String contentType;
private String method;
private String version;
private String publicDoctype;
private String systemDoctype;
private String encoding;
private boolean indent;
private int indentAmount;
private String directory;
private String file;
private boolean omitXMLDeclaration;
private Boolean standalone;
boolean cacheUseLocalCache;
public Config(Document document) {
// Directory and file
directory = XPathUtils.selectStringValueNormalize(document, "/config/directory");
file = XPathUtils.selectStringValueNormalize(document, "/config/file");
// Content-type
contentType = XPathUtils.selectStringValueNormalize(document, "/config/content-type");
if (contentType == null) contentType = DEFAULT_CONTENT_TYPE;
// Method
if (HTML_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
method = HTML_METHOD;
} else if (XML_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
method = XML_METHOD;
} else if (TEXT_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
method = TEXT_METHOD;
} else
throw new OXFException("Invalid content-type" + contentType);
// Version
version = XPathUtils.selectStringValueNormalize(document, "/config/version");
if (version == null) {
if (method.equals(XML_METHOD))
version = DEFAULT_XML_VERSION;
else if (method.equals(HTML_METHOD))
version = null;
}
// Public doctype
publicDoctype = XPathUtils.selectStringValueNormalize(document, "/config/public-doctype");
// System doctype
systemDoctype = XPathUtils.selectStringValueNormalize(document, "/config/system-doctype");
// Encoding
encoding = XPathUtils.selectStringValueNormalize(document, "/config/encoding");
if (encoding == null) {
encoding = DEFAULT_ENCODING;
}
// Indent
String indentString = XPathUtils.selectStringValueNormalize(document, "/config/indent");
if (indentString == null)
indent = DEFAULT_INDENT;
else
indent = new Boolean(indentString).booleanValue();
// Indent amount
Integer indentAmountInteger = XPathUtils.selectIntegerValue(document, "/config/indent-amount");
if (indentAmountInteger == null)
indentAmount = DEFAULT_INDENT_AMOUNT;
else
indentAmount = indentAmountInteger.intValue();
// Omit XML declaration
String omitXMLDeclarationString = XPathUtils.selectStringValueNormalize(document, "/config/omit-xml-declaration");
if (omitXMLDeclarationString != null)
omitXMLDeclaration = new Boolean(omitXMLDeclarationString).booleanValue();
// Standalone
String standaloneString = XPathUtils.selectStringValueNormalize(document, "/config/standalone");
if (standaloneString != null)
standalone = new Boolean(standaloneString);
// Cache control
String cacheUseLocalCacheString = XPathUtils.selectStringValueNormalize(document, "/config/cache-control/use-local-cache");
if (cacheUseLocalCacheString == null)
cacheUseLocalCache = CachedSerializer.DEFAULT_CACHE_USE_LOCAL_CACHE;
else
cacheUseLocalCache = new Boolean(cacheUseLocalCacheString).booleanValue();
}
public String getContentType() {
return contentType;
}
public String getMethod() {
return method;
}
public String getVersion() {
return version;
}
public String getPublicDoctype() {
return publicDoctype;
}
public String getSystemDoctype() {
return systemDoctype;
}
public String getEncoding() {
return encoding;
}
public boolean isOmitXMLDeclaration() {
return omitXMLDeclaration;
}
public Boolean isStandalone() {
return standalone;
}
public boolean isIndent() {
return indent;
}
public int getIndentAmount() {
return indentAmount;
}
public String getDirectory() {
return directory;
}
public String getFile() {
return file;
}
}
public void start(PipelineContext context) {
try {
// Read config
final Config config = readCacheInputAsObject(context, getInputByName(INPUT_CONFIG), new CacheableInputReader<Config>() {
public Config read(PipelineContext context, ProcessorInput input) {
return new Config(readInputAsOrbeonDom(context, input));
}
});
ProcessorInput dataInput = getInputByName(INPUT_DATA);
// Check that directory is ok
File file;
String directoryProperty = getPropertySet().getString(DIRECTORY_PROPERTY);
if (directoryProperty == null && config.getDirectory() == null) {
// No base directory specified
file = new File(config.getFile());
} else {
// Base directory specified
File baseDirectory = directoryProperty != null ? new File(directoryProperty) : new File(config.getDirectory());
if (!baseDirectory.isDirectory() || !baseDirectory.canWrite())
throw new OXFException("Directory '" + baseDirectory + "' is not a directory or is not writeable.");
file = new File(baseDirectory, config.getFile());
}
// NOTE: Caching here is broken. This is what we need to do:
// - for a given file, store a hash of the content stored (or the input key?)
// - then when we check whether we need to modify the file, check against the key
// AND the validity
// Compute last modified
// Object validity = getInputValidity(context, dataInput);
// long now = System.currentTimeMillis();
// long lastModified = (validity != null) ? findLastModified(validity) : now;
// boolean cacheable = validity != null && lastModified != 0;
// if (lastModified == 0)
// lastModified = now;
//
// if (logger.isDebugEnabled())
// logger.debug("Last modified: " + lastModified);
//
// // Check lastModified and don't return content if condition is met
// if (cacheable && (lastModified <= (file.lastModified() + 1000))) {
// if (logger.isDebugEnabled())
// logger.debug("File doesn't need rewrite");
// return;
// }
// Delete file if it exists
if (file.exists() && file.canWrite())
file.delete();//TODO: make sure the file was deleted
// Create file
if (!file.createNewFile())
throw new OXFException("Can't create file: " + file);
// Create Writer and make sure it is closed when the pipeline terminates
final OutputStream fileOutputStream = new FileOutputStream(file);
context.addContextListener(new PipelineContext.ContextListenerAdapter() {
public void contextDestroyed(boolean success) {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new OXFException(e);
}
}
});
if (config.cacheUseLocalCache) {
// If caching of the data is enabled, use the caching API
// We return a ResultStore
final boolean[] read = new boolean[1];
ResultStore filter = readCacheInputAsObject(context, dataInput, new CacheableInputReader<ResultStore>() {
public ResultStore read(PipelineContext context, ProcessorInput input) {
read[0] = true;
if (logger.isDebugEnabled())
logger.debug("Output not cached");
try {
ResultStoreOutputStream resultStoreOutputStream = new ResultStoreOutputStream(fileOutputStream);
readInput(context, null, input, config, resultStoreOutputStream);
resultStoreOutputStream.close();
return resultStoreOutputStream;
} catch (IOException e) {
throw new OXFException(e);
}
}
});
// If the output was obtained from the cache, just write it
if (!read[0]) {
if (logger.isDebugEnabled())
logger.debug("Serializer output cached");
filter.replay(fileOutputStream);
}
} else {
// Caching is not enabled
readInput(context, null, dataInput, config, fileOutputStream);
fileOutputStream.close();
}
} catch (Exception e) {
throw new OXFException(e);
}
}
protected void readInput(PipelineContext context, ExternalContext.Response response, ProcessorInput input, Object _config, OutputStream outputStream) {
FileSerializer.Config config = (FileSerializer.Config) _config;
Writer writer = getWriter(outputStream, config);
// Create an identity transformer and start the transformation
final TransformerXMLReceiver identity = TransformerUtils.getIdentityTransformerHandler();
TransformerUtils.applyOutputProperties(identity.getTransformer(),
config.getMethod(), config.getVersion(), config.getPublicDoctype(),
config.getSystemDoctype(), config.getEncoding(), config.isOmitXMLDeclaration(), config.isStandalone(),
config.isIndent(), config.getIndentAmount());
identity.setResult(new StreamResult(writer));
readInputAsSAX(context, input, new SerializerXMLReceiver(identity, writer, getPropertySet().getBoolean("serialize-xml-11", false)));
}
protected Writer getWriter(OutputStream outputStream, Config config) {
try {
return new OutputStreamWriter(outputStream, config.encoding);
} catch (UnsupportedEncodingException e) {
throw new OXFException(e);
}
}
}