| /* |
| * 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.lenya.cms.editors.fckeditor; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.util.Arrays; |
| import java.util.Properties; |
| import javax.xml.transform.stream.StreamSource; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.OutputKeys; |
| |
| import org.apache.cocoon.components.ContextHelper; |
| import org.apache.cocoon.environment.Request; |
| import org.apache.cocoon.xml.XMLUtils; |
| import org.apache.excalibur.source.ModifiableSource; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.apache.lenya.cms.linking.LinkConverter; |
| import org.apache.lenya.cms.linking.LinkRewriter; |
| import org.apache.lenya.cms.linking.OutgoingLinkRewriter; |
| import org.apache.lenya.cms.publication.ResourceType; |
| import org.apache.lenya.cms.publication.URLInformation; |
| import org.apache.lenya.cms.usecase.DocumentUsecase; |
| import org.apache.lenya.cms.usecase.UsecaseException; |
| import org.apache.lenya.cms.usecase.xml.UsecaseErrorHandler; |
| import org.apache.lenya.cms.workflow.WorkflowUtil; |
| import org.apache.lenya.cms.workflow.usecases.UsecaseWorkflowHelper; |
| import org.apache.lenya.xml.DocumentHelper; |
| import org.apache.lenya.xml.Schema; |
| import org.apache.lenya.xml.ValidationUtil; |
| import org.w3c.dom.Document; |
| import org.xml.sax.SAXException; |
| import org.w3c.tidy.Tidy; |
| |
| /** |
| * Fckeditor Usecase |
| * |
| */ |
| public class Fckeditor extends DocumentUsecase { |
| |
| public static final String TIDY_CONFIG = "tidyConfig"; |
| public static final String XSLT_CLEAN_FORMAT = "xslt-clean"; |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#getNodesToLock() |
| */ |
| protected org.apache.lenya.cms.repository.Node[] getNodesToLock() throws UsecaseException { |
| org.apache.lenya.cms.repository.Node[] objects = { getSourceDocument().getRepositoryNode() }; |
| return objects; |
| } |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#initParameters() |
| */ |
| protected void initParameters() { |
| super.initParameters(); |
| |
| Request request = ContextHelper.getRequest(this.context); |
| String requesturi = request.getRequestURI(); |
| setParameter("requesturi", requesturi); |
| URLInformation info = new URLInformation(getSourceURL()); |
| String pubId = info.getPublicationId(); |
| LinkRewriter rewriter = new OutgoingLinkRewriter(this.manager, getSession(), |
| getSourceURL(), request.isSecure(), false, false); |
| |
| setParameter("proxyUrl",rewriter.rewrite("/" + pubId)); |
| } |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#doCheckPreconditions() |
| */ |
| protected void doCheckPreconditions() throws Exception { |
| super.doCheckPreconditions(); |
| |
| UsecaseWorkflowHelper.checkWorkflow(this.manager, this, getEvent(), getSourceDocument(), |
| getLogger()); |
| } |
| |
| /** |
| * @see org.apache.lenya.cms.usecase.AbstractUsecase#doExecute() |
| */ |
| protected void doExecute() throws Exception { |
| super.doExecute(); |
| |
| // Get namespaces |
| String namespaces = removeRedundantNamespaces(getParameterAsString("namespaces")); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug(namespaces); |
| } |
| |
| // Aggregate content |
| Request request = ContextHelper.getRequest(this.context); |
| String encoding = request.getCharacterEncoding(); |
| String content = "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n" |
| + addNamespaces(namespaces, getParameterAsString("content")); |
| saveDocument(encoding, content); |
| } |
| |
| /** |
| * Save the content to the document source. After saving, the XML is |
| * validated. If validation errors occur, the usecase transaction is rolled |
| * back, so the changes are not persistent. If the validation succeeded, the |
| * workflow event is invoked. |
| * @param encoding The encoding to use. |
| * @param content The content to save. |
| * @throws Exception if an error occurs. |
| */ |
| protected void saveDocument(String encoding, String content) throws Exception { |
| SourceResolver resolver = null; |
| Source indexSource = null; |
| Source tidySource = null; |
| ModifiableSource xsltSource = null; |
| Properties properties = null; |
| try { |
| resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE); |
| saveXMLFile(encoding, content, getSourceDocument().getOutputStream()); |
| |
| Document xmlDoc = null; |
| |
| // Setup an instance of Tidy. |
| Tidy tidy = new Tidy(); |
| |
| String tidyProps = this.getParameterAsString(TIDY_CONFIG, null); |
| if (tidyProps != null) { |
| tidySource = resolver.resolveURI(tidyProps); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Loading configuration from " + tidySource.getURI()); |
| } |
| properties = new Properties(); |
| properties.load(tidySource.getInputStream()); |
| |
| if (properties == null) { |
| tidy.setXHTML(true); |
| } else { |
| tidy.setConfigurationFromProps(properties); |
| } |
| |
| // Set Jtidy warnings on-off |
| tidy.setShowWarnings(getLogger().isWarnEnabled()); |
| // Set Jtidy final result summary on-off |
| tidy.setQuiet(!getLogger().isInfoEnabled()); |
| // Set Jtidy infos to a String (will be logged) instead of |
| // System.out |
| StringWriter stringWriter = new StringWriter(); |
| PrintWriter errorWriter = new PrintWriter(stringWriter); |
| tidy.setErrout(errorWriter); |
| |
| xmlDoc = tidy.parseDOM(getSourceDocument().getInputStream(), null); |
| |
| // FIXME: Jtidy doesn't warn or strip duplicate attributes in |
| // same |
| // tag; stripping. |
| XMLUtils.stripDuplicateAttributes(xmlDoc, null); |
| |
| StringWriter output = new StringWriter(); |
| StreamResult strResult = new StreamResult(output); |
| TransformerFactory tfac = TransformerFactory.newInstance(); |
| try { |
| Transformer t = tfac.newTransformer(); |
| t.setOutputProperty(OutputKeys.ENCODING, encoding); |
| t.setOutputProperty(OutputKeys.INDENT, "yes"); |
| t.setOutputProperty(OutputKeys.METHOD, "xml"); |
| t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); |
| t.transform(new DOMSource(xmlDoc.getDocumentElement()), strResult); |
| |
| content = strResult.getWriter().toString(); |
| } catch (Exception e) { |
| addErrorMessage(e.getMessage()); |
| } |
| |
| saveXMLFile(encoding, content, getSourceDocument().getOutputStream()); |
| } else { |
| try { |
| xmlDoc = DocumentHelper.readDocument(getSourceDocument().getInputStream()); |
| } catch (SAXException e) { |
| addErrorMessage("error-document-form", new String[] { e.getMessage() }); |
| } |
| } |
| |
| |
| |
| // Try to clean the xml using xslt |
| ResourceType resType = getSourceDocument().getResourceType(); |
| String[] formats = resType.getFormats(); |
| if (Arrays.asList(formats).contains(XSLT_CLEAN_FORMAT)) { |
| StringWriter output = new StringWriter(); |
| StreamResult strResult = new StreamResult(output); |
| TransformerFactory tfac = TransformerFactory.newInstance(); |
| try { |
| xsltSource = (ModifiableSource) resolver.resolveURI(resType |
| .getFormatURI(XSLT_CLEAN_FORMAT)); |
| Transformer t = tfac.newTransformer(new StreamSource(xsltSource |
| .getInputStream())); |
| t.setOutputProperty(OutputKeys.ENCODING, encoding); |
| t.setOutputProperty(OutputKeys.INDENT, "yes"); |
| t.setOutputProperty(OutputKeys.METHOD, "xml"); |
| t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); |
| xmlDoc = DocumentHelper.readDocument(getSourceDocument().getInputStream()); |
| t.transform(new DOMSource(xmlDoc.getDocumentElement()), strResult); |
| |
| content = strResult.getWriter().toString(); |
| } catch (Exception e) { |
| addErrorMessage(e.getMessage()); |
| } |
| |
| saveXMLFile(encoding, content, getSourceDocument().getOutputStream()); |
| } |
| // Convert URLs back to UUIDs. convert() does a save |
| LinkConverter converter = new LinkConverter(this.manager, getLogger()); |
| converter.convertUrlsToUuids(getSourceDocument(),false); |
| |
| xmlDoc = DocumentHelper.readDocument(getSourceDocument().getInputStream()); |
| |
| if (xmlDoc != null) { |
| ResourceType resourceType = getSourceDocument().getResourceType(); |
| Schema schema = resourceType.getSchema(); |
| |
| ValidationUtil |
| .validate(this.manager, xmlDoc, schema, new UsecaseErrorHandler(this)); |
| |
| if (!hasErrors()) { |
| WorkflowUtil.invoke(this.manager, getSession(), getLogger(), |
| getSourceDocument(), getEvent()); |
| } |
| } |
| |
| } finally { |
| if (resolver != null) { |
| if (indexSource != null) { |
| resolver.release(indexSource); |
| } |
| if (tidySource != null) { |
| resolver.release(tidySource); |
| } |
| if (xsltSource != null) { |
| resolver.release(xsltSource); |
| } |
| this.manager.release(resolver); |
| } |
| } |
| } |
| |
| /** |
| * Save the XML file |
| * @param encoding The encoding |
| * @param content The content |
| * @param out The stream to write to |
| * @throws FileNotFoundException if the file was not found |
| * @throws UnsupportedEncodingException if the encoding is not supported |
| * @throws IOException if an IO error occurs |
| */ |
| private void saveXMLFile(String encoding, String content, OutputStream out) |
| throws FileNotFoundException, UnsupportedEncodingException, IOException { |
| Writer writer = null; |
| |
| try { |
| writer = new OutputStreamWriter(out, encoding); |
| writer.write(content, 0, content.length()); |
| } catch (FileNotFoundException e) { |
| getLogger().error("File not found " + e.toString()); |
| } catch (UnsupportedEncodingException e) { |
| getLogger().error("Encoding not supported " + e.toString()); |
| } catch (IOException e) { |
| getLogger().error("IO error " + e.toString()); |
| } finally { |
| // close all streams |
| if (writer != null) |
| writer.close(); |
| } |
| } |
| |
| /** |
| * Remove redundant namespaces |
| * @param namespaces The namespaces to remove |
| * @return The namespace string without the removed namespaces |
| */ |
| private String removeRedundantNamespaces(String namespaces) { |
| String[] namespace = namespaces.split(" "); |
| |
| String ns = ""; |
| for (int i = 0; i < namespace.length; i++) { |
| if (ns.indexOf(namespace[i]) < 0) { |
| ns = ns + " " + namespace[i]; |
| } else { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Redundant namespace: " + namespace[i]); |
| } |
| } |
| } |
| return ns; |
| } |
| |
| /** |
| * Add namespaces |
| * @param namespaces The namespaces to add |
| * @param content The content to add them to |
| * @return The content with the added namespaces |
| */ |
| private String addNamespaces(String namespaces, String content) { |
| int i = content.indexOf(">"); |
| return content.substring(0, i) + " " + namespaces + content.substring(i); |
| } |
| |
| protected String getEvent() { |
| return "edit"; |
| } |
| |
| } |