/* * Copyright (C) 2008-2012 Open Wide SA * * This library is free software; you can redistribute * it and/or modify it under the terms of version 2.1 of * the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * * More information at http://knowledge.openwide.fr/bin/view/Main/AlfrescoETLConnector/ */ package fr.openwide.talendalfresco.acpxml; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import fr.openwide.talendalfresco.rest.XmlHelper; /** * Writes ACP XML using stax * * @author Marc Dutoo - Open Wide SA * */ public class AcpXmlWriter { /** * Alfresco date format. * NB. Alfresco date (see ISO8601DateFormat : sYYYY-MM-DDThh:mm:ss.sssTZD */ protected static SimpleDateFormat alfrescoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); // Z protected static final String ISO8601_TIMEZONE_SUFFIX = "+00:00"; protected XMLStreamWriter xmlWriter; /** * Possibly also encoding * @param acpXmlOs * @param contentNamespaceMap list of mapped PREFIX, URI * @param encoding NB. default to work with tomcat is ISO-8859-1 * @throws XMLStreamException */ public AcpXmlWriter(OutputStream acpXmlOs, String encoding) throws AcpXmlException { this(acpXmlOs, encoding, null); } public AcpXmlWriter(OutputStream acpXmlOs, String encoding, List<Map<String,String>> mappedContentNamespaces) throws AcpXmlException { try { xmlWriter = XmlHelper.createXMLStreamWriter(acpXmlOs, encoding); // write document start //xmlWriter.setNamespaceContext(nsCtx); // not useful xmlWriter.writeStartDocument(); // default version ; possibly encoding xmlWriter.writeStartElement("view:view"); xmlWriter.writeNamespace("view", "http://www.alfresco.org/view/repository/1.0"); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when starting ACP", e); } if (mappedContentNamespaces != null) { for (Map<String,String> mappedContentNamespace : mappedContentNamespaces) { String contentNsPrefix = mappedContentNamespace.get("PREFIX"); String contentNsUri = mappedContentNamespace.get("URI"); this.writeNamespace(contentNsPrefix, contentNsUri); } } } public void close() throws AcpXmlException { try { xmlWriter.writeEndElement(); // end view xmlWriter.writeEndDocument(); xmlWriter.close(); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when ending ACP", e); } } public void writeNamespace(String contentNsPrefix, String contentNsUri) throws AcpXmlException { try { xmlWriter.writeNamespace(contentNsPrefix, contentNsUri); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * * @param targetLocation * @param alfrescoType prefixed alfresco type ; if null cm:content ? * @param alfrescoAspects prefixed alfresco aspects * @param configurePermissions if false none * @param inheritPermissions * @param permissions list of mapped USERORGROUP (here should be default or runtime found value TODO or other param ?), * USERORGROUPCOLUMN (not used), PERMISSION * @throws AcpXmlException */ public void writeStartContent(String alfrescoType, String childName) throws AcpXmlException { writeStartContent(alfrescoType); try { if (childName != null && childName.length() != 0) { xmlWriter.writeAttribute("view:childName", childName); // opt childname } } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeStartContent(String alfrescoType) throws AcpXmlException{ try { xmlWriter.writeStartElement(alfrescoType); // "cm:content" } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeStartPermissions(boolean inheritPermissions) throws AcpXmlException { try { xmlWriter.writeStartElement("view:acl"); xmlWriter.writeAttribute("view:inherit", String.valueOf(inheritPermissions)); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writePermission(Map<String, String> permissionMapping, Object optUserOrGroupColumnValue) throws AcpXmlException { String authority = (optUserOrGroupColumnValue != null) ? String.valueOf(optUserOrGroupColumnValue) : permissionMapping.get("USERORGROUP"); String permission = permissionMapping.get("PERMISSION"); writePermission(authority, permission); } /** * To allow direct access to permission writing, ex. to write the container's * @param permissionMapping * @param optUserOrGroupColumnValue * @throws AcpXmlException */ public void writePermission(String authority, String permission) throws AcpXmlException { try { xmlWriter.writeStartElement("view:ace"); xmlWriter.writeAttribute("view:access", "ALLOWED"); // only ALLOWED is possible xmlWriter.writeStartElement("view:authority"); xmlWriter.writeCharacters(authority); // "GROUP_EVERYONE" xmlWriter.writeEndElement(); xmlWriter.writeStartElement("view:permission"); xmlWriter.writeCharacters(permission); // "Consumer" xmlWriter.writeEndElement(); xmlWriter.writeEndElement(); // end ace } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeEndPermissions() throws AcpXmlException { try { xmlWriter.writeEndElement(); // end view:acl } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * * @param alfrescoAspects list of mapped NAME * @throws AcpXmlException */ public void writeMappedAspects(List<Map<String,String>> alfrescoAspects) throws AcpXmlException{ writeStartAspects(); // aspects if (alfrescoAspects != null) { for (Map<String,String> alfrescoAspect : alfrescoAspects) { String alfrescoAspectName = alfrescoAspect.get("NAME"); this.writeAspect(alfrescoAspectName); // "app:uifacets" } } writeEndAspects(); } public void writeAspects(List<String> alfrescoAspectNames) throws AcpXmlException{ writeStartAspects(); // aspects if (alfrescoAspectNames != null) { for (String alfrescoAspectName : alfrescoAspectNames) { this.writeAspect(alfrescoAspectName); // "app:uifacets" } } writeEndAspects(); } public void writeStartAspects() throws AcpXmlException{ try { xmlWriter.writeStartElement("view:aspects"); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * Must be between writeStartAspects & writeEndAspects in a document * @param alfrescoAspectName * @throws AcpXmlException */ public void writeAspect(String alfrescoAspectName) throws AcpXmlException{ try { xmlWriter.writeEmptyElement(alfrescoAspectName); // "app:uifacets" } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content : aspect " + alfrescoAspectName, e); } } public void writeEndAspects() throws AcpXmlException{ try { xmlWriter.writeEndElement(); // end view:aspects } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeStartProperties() throws AcpXmlException{ try { xmlWriter.writeStartElement("view:properties"); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * Must be between writeStartProperties() & writeEndProperties() in a document * @param propertyMappings prefixed alfresco property ; mapped * NAME, TITLE (not used), TYPE (used for conversion ?? TODO), * MANDATORY (could be used for check ?), DEFAULT (not used ?), * COLUMN (not used), VALUE (here is talend-side default if any) * @param value list if multiple, else toString() serializes it ; * if null takes talend-side default (VALUE) * @throws AcpXmlException */ public void writeMappedProperty(Map<String, String> propertyMapping, Object value) throws AcpXmlException{ String propertyName = propertyMapping.get("NAME"); String propertyType = propertyMapping.get("TYPE"); if (value == null) { // getting talend-side default value = propertyMapping.get("VALUE"); } writeProperty(propertyName, propertyType, value); } /** * Must be between writeStartProperties() & writeEndProperties() in a document * To be used for direct access, ex. write name of containers. * @param propertyName * @param propertyType * @param value * @throws AcpXmlException */ @SuppressWarnings("unchecked") public void writeProperty(String propertyName, String propertyType, Object value) throws AcpXmlException { try { if (value instanceof List) { // multivalued property ; or based on MULTIPLE TODO in model // converting it to Strings first, to prevent any error during actual writing List<Object> values = (List<Object>) value; ArrayList<String> stringValues = new ArrayList<String>(values.size()); try { for (Object currentValue : (List<Object>) values) { stringValues.add(valueToString(currentValue, propertyType)); } } catch (AcpXmlException acpxmlex) { throw new AcpXmlException("Aborted writing" + " multi valued property " + propertyName, acpxmlex); } // actual writing xmlWriter.writeStartElement(propertyName); // "cm:name" xmlWriter.writeStartElement("view:values"); for (String stringValue : stringValues) { xmlWriter.writeStartElement("view:value"); xmlWriter.writeCharacters(stringValue); // "/cm:generalclassifiable/cm:Languages/cm:French" xmlWriter.writeEndElement(); // end view:value } xmlWriter.writeEndElement(); // end view:values xmlWriter.writeEndElement(); } else if (value != null) { // single valued property (or multiple property with only one value) // converting it to Strings first, to prevent any error during actual writing String stringValue; try { stringValue = valueToString(value, propertyType); } catch (AcpXmlException acpxmlex) { throw new AcpXmlException("Aborted writing" + " single valued property " + propertyName, acpxmlex); } // actual writing xmlWriter.writeStartElement(propertyName); // "cm:name" xmlWriter.writeCharacters(stringValue); // "my_file_writer.pdf" ; NB. CDATA works, but rather using auto escaping xmlWriter.writeEndElement(); } // else null : don't write it (allowed to ease mapping from etl) } catch (AcpXmlException e) { throw e; } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content : property " + propertyName, e); } } public void writeEndProperties() throws AcpXmlException{ try { xmlWriter.writeEndElement(); // end view:properties } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * Converts a single value of the given property type from its Talend * representation to its String-serialized Alfresco representation. * For a content property (d:content), it also returns mimetype etc. * An Alfresco date (d:date) expects the Talend type to be DATE (java.util.Date). * @param value * @param alfrescoPropertyType * @return * @throws AcpXmlException ex. if date property value not a Date */ protected String valueToString(Object value, String alfrescoPropertyType) throws AcpXmlException { if ("d:content".equals(alfrescoPropertyType)) { // content property ; ex. contentUrl=classpath:alfresco/bootstrap/Alfresco-Tutorial.pdf|mimetype=application/pdf|size=|encoding= String contentUrl = String.valueOf(value); // "classpath:alfresco/bootstrap/Alfresco-Tutorial.pdf" return "contentUrl=" + contentUrl + "|mimetype=|size=|encoding="; // TODO mimetype ? (encoding ??) } else if ("d:noderef".equals(alfrescoPropertyType) || "d:category".equals(alfrescoPropertyType)) { // reference ; let's mark it to be resolved (will be done by contentImporterBinding on the server) if (value instanceof String) { return AcpXmlUtil.toNamePathRef((String) value); } throw new AcpXmlException("Value of " + alfrescoPropertyType + " property should be of type String but is " + value); } else if ("d:date".equals(alfrescoPropertyType)) { if (value instanceof Date) { return alfrescoDateFormat.format((Date) value) + ISO8601_TIMEZONE_SUFFIX; } throw new AcpXmlException("Value of d:date property should be of type Date but is " + value); /* TODO byte array encoding } else if (value instanceof byte[]) { return String.valueOf((byte[]) value, encoding); */ } else { // other types : only stringify return String.valueOf(value); // "my_file_writer.pdf" ; NB. CDATA works, but rather using auto escaping } } public void writeStartAssociations() throws AcpXmlException{ try { xmlWriter.writeStartElement("view:associations"); } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * Must be between writeStartAssociations() & writeEndAssociations() in a document * @param associationMappings prefixed alfresco (non primary child) associations ; list of mapped * NAME, CHILD (not used), TITLE (not used), TYPE (used for conversion ?? TODO), * MANDATORY (could be used for check ?), MANY (not used), * COLUMN (not used), new ! VALUE (here should be runtime found namepath ref TODO or other param ?) * @param ref if List, multiple TODO ? else anything that the server importer binding can understand * (in alfresco talend, namepathref is OK TODO) * @throws AcpXmlException */ public void writeMappedAssociation(Map<String, String> associationMappings, Object value) throws AcpXmlException { String associationName = associationMappings.get("NAME"); writeAssociation(associationName, value); } /** * Must be between writeAssociationsStart() & writeAssociationsEnd() in a document * @param associationName * @param value * @throws AcpXmlException */ @SuppressWarnings("unchecked") public void writeAssociation(String associationName, Object value) throws AcpXmlException { try { if (value instanceof List) { // multivalued asso // converting it to Strings first, to prevent any error during actual writing List<String> valueNamePathes; try { valueNamePathes = AcpXmlUtil.toStringList((List<Object>) value); } catch (AcpXmlException acpxmlex) { throw new AcpXmlException("Aborted writing" + " multi valued association " + associationName, acpxmlex); } // actual writing xmlWriter.writeStartElement(associationName); // "cm:contains" for (Object currentNamePath : valueNamePathes) { xmlWriter.writeStartElement("view:reference"); xmlWriter.writeAttribute("view:pathref", AcpXmlUtil.toNamePathRef((String) currentNamePath)); xmlWriter.writeEndElement(); // end reference } xmlWriter.writeEndElement(); // end association } else if (value != null) { // single valued association (or multiple association with only one value) // converting it to String first, to prevent any error during actual writing if (!(value instanceof String)) { throw new AcpXmlException("Reference value " + value + " of association " + associationName + " should be of type String"); } // actual writing xmlWriter.writeStartElement(associationName); // "cm:contains" xmlWriter.writeStartElement("view:reference"); xmlWriter.writeAttribute("view:pathref", AcpXmlUtil.toNamePathRef((String) value)); xmlWriter.writeEndElement(); // end reference xmlWriter.writeEndElement(); // end association } // else null : don't write it (allowed to ease mapping from etl) } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content : association " + associationName, e); } } /** * Must be between writeStartAssociations() & writeEndAssociations() in a document * containment association. * Stacks up path name. Requires the "cm:name" property to have already been set. * @param parentChildAssociationType * @throws AcpXmlException */ public void writeStartPrimaryChildAssociation(String parentChildAssociationType) throws AcpXmlException{ try { xmlWriter.writeStartElement(parentChildAssociationType); // "cm:contains" //xmlWriter.writeAttribute("view:childName", parentChildAssociationType); // default childname } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } /** * Stacks down path name. * @throws AcpXmlException */ public void writeEndPrimaryChildAssociation() throws AcpXmlException{ try { xmlWriter.writeEndElement(); // end cm:contains } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeEndAssociations() throws AcpXmlException{ try { xmlWriter.writeEndElement(); // end view:associations } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } public void writeEndContent() throws AcpXmlException{ try { xmlWriter.writeEndElement(); // end cm:content } catch (XMLStreamException e) { throw new AcpXmlException("XML writing error when writing content", e); } } }