001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.xml.content;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.main.CmsLog;
035import org.opencms.main.CmsRuntimeException;
036import org.opencms.xml.CmsXmlContentDefinition;
037import org.opencms.xml.CmsXmlUtils;
038
039import java.io.ByteArrayInputStream;
040import java.util.ArrayList;
041import java.util.List;
042
043import javax.xml.parsers.DocumentBuilder;
044import javax.xml.parsers.DocumentBuilderFactory;
045import javax.xml.transform.ErrorListener;
046import javax.xml.transform.OutputKeys;
047import javax.xml.transform.Source;
048import javax.xml.transform.Transformer;
049import javax.xml.transform.TransformerException;
050import javax.xml.transform.TransformerFactory;
051import javax.xml.transform.dom.DOMResult;
052import javax.xml.transform.dom.DOMSource;
053import javax.xml.transform.sax.SAXSource;
054
055import org.apache.commons.logging.Log;
056import org.apache.xerces.parsers.SAXParser;
057
058import org.dom4j.Document;
059import org.xml.sax.ErrorHandler;
060import org.xml.sax.InputSource;
061import org.xml.sax.SAXException;
062import org.xml.sax.SAXParseException;
063
064/**
065 * Provides static methods for XML content version transformations.
066 */
067public class CmsVersionTransformer {
068
069    /** Logger for this class. */
070    private static final Log LOG = CmsLog.getLog(CmsVersionTransformer.class);
071
072    /** XSL parameter for passing a context object to version transformations. */
073    public static final String XSL_PARAM_TRANSFORMATION_CONTEXT = "context";
074
075    /**
076     * Converts an XML content document to the current version using the version transformation XSLT file which is configured in the schema.
077     *
078     * @param cms the current CMS context
079     * @param document the document to transform
080     * @param contentDefinition the content definition for which we are doing the conversion
081     *
082     * @return the converted document
083     */
084    @SuppressWarnings("synthetic-access")
085    public static Document transformDocumentToCurrentVersion(
086        CmsObject cms,
087        Document document,
088        CmsXmlContentDefinition contentDefinition) {
089
090        String transformation = contentDefinition.getContentHandler().getVersionTransformation();
091        if (transformation == null) {
092            LOG.warn(
093                "Schema version detected, but no version transformation defined for "
094                    + contentDefinition.getSchemaLocation());
095            return document;
096        }
097
098        try {
099            CmsResource xsltResource = cms.readResource(transformation, CmsResourceFilter.IGNORE_EXPIRATION);
100            CmsFile xsltFile = cms.readFile(xsltResource);
101            // we explicitly want an Xalan transformer factory here, even if we add some other XSLT implementation later,
102            // because we rely on specific Xalan features (the way extension functions work).
103            TransformerFactory transformerFactory = new org.apache.xalan.processor.TransformerFactoryImpl();
104            List<Exception> errors = new ArrayList<>();
105            transformerFactory.setErrorListener(new ErrorListener() {
106
107                public void error(TransformerException e) throws TransformerException {
108
109                    errors.add(e);
110                    throw e;
111
112                }
113
114                public void fatalError(TransformerException e) throws TransformerException {
115
116                    errors.add(e);
117                    throw e;
118                }
119
120                public void warning(TransformerException e) {
121
122                    LOG.warn(e.getLocalizedMessage(), e);
123                }
124            });
125            SAXSource transformationSource = new SAXSource(
126                new InputSource(new ByteArrayInputStream(xsltFile.getContents())));
127            SAXParser parser = new SAXParser();
128            parser.setErrorHandler(new ErrorHandler() {
129
130                public void error(SAXParseException e) throws SAXException {
131
132                    errors.add(e);
133                    throw e;
134
135                }
136
137                public void fatalError(SAXParseException e) throws SAXException {
138
139                    errors.add(e);
140                    throw e;
141
142                }
143
144                public void warning(SAXParseException e) {
145
146                    LOG.warn(e.getLocalizedMessage(), e);
147
148                }
149            });
150            transformationSource.setXMLReader(parser);
151            Transformer transformer = transformerFactory.newTransformer(transformationSource);
152            if (errors.size() > 0) {
153                throw errors.get(0);
154            }
155            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
156
157            Source source = new DOMSource(CmsXmlUtils.convertDocumentFromDom4jToW3C(document));
158            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
159            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
160            org.w3c.dom.Document targetDoc = builder.newDocument();
161            DOMResult target = new DOMResult(targetDoc);
162            transformer.setParameter(XSL_PARAM_TRANSFORMATION_CONTEXT, new CmsXsltContext(cms));
163            transformer.transform(source, target);
164            if (errors.size() > 0) {
165                throw errors.get(0);
166            }
167            Document result = CmsXmlUtils.convertDocumentFromW3CToDom4j(targetDoc);
168            result.getRootElement().addAttribute(CmsXmlContent.A_VERSION, "" + contentDefinition.getVersion());
169            if (LOG.isDebugEnabled()) {
170                try {
171                    LOG.debug(
172                        "Used XSL transformation "
173                            + transformation
174                            + "\n----------------------------"
175                            + "\nOriginal XML:"
176                            + "\n----------------------------\n"
177                            + CmsXmlUtils.marshal(document, "UTF-8")
178                            + "\n----------------------------\nTransformed XML:"
179                            + "\n----------------------------\n"
180                            + CmsXmlUtils.marshal(result, "UTF-8"));
181                } catch (Exception e) {
182                    LOG.error(e.getLocalizedMessage(), e);
183                }
184            }
185            return result;
186        } catch (Exception e) {
187            LOG.error(e.getLocalizedMessage(), e);
188            throw new CmsRuntimeException(
189                Messages.get().container(Messages.ERR_XMLCONTENT_VERSION_TRANSFORMATION_ERROR_1, transformation),
190                e);
191        }
192    }
193
194}