/** * 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); } } }