/** Copyright (C) 2012 Delcyon, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.delcyon.capo.controller.elements; import java.io.OutputStream; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.delcyon.capo.CapoApplication; import com.delcyon.capo.Configuration.PREFERENCE; import com.delcyon.capo.controller.AbstractControl; import com.delcyon.capo.controller.ControlElementProvider; import com.delcyon.capo.datastream.NullOutputStream; import com.delcyon.capo.datastream.stream_attribute_filter.MD5FilterOutputStream; import com.delcyon.capo.resourcemanager.ResourceDescriptor; import com.delcyon.capo.resourcemanager.ResourceParameter; import com.delcyon.capo.resourcemanager.ResourceParameterBuilder; import com.delcyon.capo.resourcemanager.types.FileResourceType; import com.delcyon.capo.resourcemanager.types.RefResourceType; import com.delcyon.capo.xml.XPath; import com.delcyon.capo.xml.cdom.CDocument; /** * @author jeremiah * */ @ControlElementProvider(name="export") public class ExportElement extends AbstractControl { private enum Attributes { name,dest,ref,nodeset,output,trim,xsl } private static final String[] supportedNamespaces = {CapoApplication.SERVER_NAMESPACE_URI}; private static final String[] namespacesToRemove = {CapoApplication.SERVER_NAMESPACE_URI,CapoApplication.CLIENT_NAMESPACE_URI}; @Override public Attributes[] getAttributes() { return Attributes.values(); } @Override public Attributes[] getRequiredAttributes() { return new Attributes[]{Attributes.dest}; } @Override public String[] getSupportedNamespaces() { return supportedNamespaces; } @Override public Object processServerSideElement() throws Exception { String ref = getAttributeValue(Attributes.ref); String name = getAttributeValue(Attributes.name); String dest = getAttributeValue(Attributes.dest); String nodeset = getAttributeValue(Attributes.nodeset); String output = getAttributeValue(Attributes.output); String trim = getAttributeValue(Attributes.trim); String xsl = getAttributeValue(Attributes.xsl); //default the name to the element name if (name == null || name.trim().isEmpty()) { name = getElementName(); } //get the list of nodes, and add them to the document Node refNode = null; if (nodeset != null && nodeset.trim().isEmpty() == false) { Document exportDocument = CapoApplication.getDocumentBuilder().newDocument(); exportDocument.appendChild(exportDocument.createElement(name)); NodeList nodeList = XPath.selectNodes(getControlElementDeclaration(), nodeset); for(int index = 0;index < nodeList.getLength(); index++) { Node node = nodeList.item(index); //strip off delcyon client and server namespace declarations ugh! We end up with multiple declarations of we don't XPath.removeNamespaceDeclarations(node, namespacesToRemove); exportDocument.getDocumentElement().appendChild(exportDocument.importNode(node, true)); } refNode = exportDocument; } else if (ref.isEmpty() == false) { refNode = XPath.selectSingleNode(getControlElementDeclaration(), ref,getControlElementDeclaration().getPrefix()); } else { refNode = getControlElementDeclaration(); } if (refNode == null) { throw new Exception("Couldn't find anything at "+ref+" to export. Check your path."); } //trim all of the whitespace out of the text nodes if (trim != null && trim.equalsIgnoreCase("true")) { refNode = refNode.cloneNode(true); NodeList nodeList = XPath.selectNodes(refNode, "descendant-or-self::*"); for(int index = 0;index < nodeList.getLength(); index++) { Node node = nodeList.item(index); String text = node.getTextContent(); if (text != null && text.isEmpty() == false) { node.setTextContent(text.trim()); } } } //escape all newline and carriage returns refNode = refNode.cloneNode(true); NodeList nodeList = XPath.selectNodes(refNode, "descendant-or-self::*"); for(int index = 0;index < nodeList.getLength(); index++) { Node node = nodeList.item(index); //make sure we only mess around with text nodes, setting the text content on a parent node will wipe out all of the children. if (node.getNodeType() != Node.TEXT_NODE) { continue; } String text = node.getTextContent(); if (text != null) { String newText = text.replaceAll("\\\\n", "\n").replaceAll("\\\\r", "\r"); node.setTextContent(newText); } } refNode.normalize(); ((CDocument) refNode.getOwnerDocument()).setVariableProcessor(getParentGroup()); TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); if(xsl.isEmpty()) { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //set any XSL output properties if (output != null && output.trim().isEmpty() == false) { String[] outputProperties = output.split(","); for (String outputProperty : outputProperties) { String propertyName = outputProperty.substring(0,outputProperty.indexOf(":")); String propertyValue = outputProperty.substring(outputProperty.indexOf(":")+1); transformer.setOutputProperty(propertyName, propertyValue); } } } else //load the user's xsl request in place of the default identity transform we use. { ResourceDescriptor xslResourceDescriptor = getParentGroup().getResourceDescriptor(this, xsl); xslResourceDescriptor.addResourceParameters(getParentGroup(),new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.RESOURCE_DIR)); xslResourceDescriptor.addResourceParameters(getParentGroup(),new ResourceParameter(RefResourceType.Parameters.XMLNS,"xsl=http://www.w3.org/1999/XSL/Transform")); transformer = tFactory.newTransformer(new StreamSource(xslResourceDescriptor.getInputStream(getParentGroup(),ResourceParameterBuilder.getResourceParameters(getControlElementDeclaration())))); xslResourceDescriptor.close(getParentGroup()); } //save the file ResourceDescriptor resourceDescriptor = getParentGroup().getResourceDescriptor(this, dest); ResourceParameter[] resourceParameters = ResourceParameterBuilder.getResourceParameters(getControlElementDeclaration()); //don't save anything if the data hasn't changed String md5 = resourceDescriptor.getResourceMetaData(getParentGroup(),resourceParameters).getMD5(); if (md5 != null) { //we are going to write this twice, but we don't know the size of the file that could be created, so this way we keep memory use to a minimum. MD5FilterOutputStream md5FilterOutputStream = new MD5FilterOutputStream(new NullOutputStream()); transformer.transform(new DOMSource(refNode), new StreamResult(md5FilterOutputStream)); if (md5.equalsIgnoreCase(md5FilterOutputStream.getMD5()) == false) { OutputStream outputStream = resourceDescriptor.getOutputStream(getParentGroup(),resourceParameters); transformer.transform(new DOMSource(refNode), new StreamResult(outputStream)); outputStream.flush(); outputStream.close(); //resourceDescriptor.getOutputStream(getParentGroup(),resourceParameters).close(); } else { //they are the same, so don't do anything } } else { OutputStream outputStream = resourceDescriptor.getOutputStream(getParentGroup(),resourceParameters); transformer.transform(new DOMSource(refNode), new StreamResult(outputStream)); outputStream.flush(); outputStream.close(); //resourceDescriptor.getOutputStream(getParentGroup(),resourceParameters).close(); } // //cleanup and parameters we added when calling this. ((CDocument) refNode.getOwnerDocument()).setVariableProcessor(null); //resourceDescriptor.close(getParentGroup(),resourceParameters); return null; } }