/* * Copyright (C) 2009 eXo Platform SAS. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.xml.importing; import org.apache.ws.commons.util.Base64; import org.apache.ws.commons.util.Base64.DecodingException; import org.exoplatform.services.jcr.access.AccessManager; import org.exoplatform.services.jcr.core.ExtendedPropertyType; import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData; import org.exoplatform.services.jcr.core.nodetype.NodeTypeData; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData; import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionDatas; import org.exoplatform.services.jcr.dataflow.ItemDataConsumer; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.datamodel.IllegalPathException; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.JCRName; import org.exoplatform.services.jcr.impl.core.LocationFactory; import org.exoplatform.services.jcr.impl.core.RepositoryImpl; import org.exoplatform.services.jcr.impl.core.value.BaseValue; import org.exoplatform.services.jcr.impl.core.value.ValueFactoryImpl; import org.exoplatform.services.jcr.impl.dataflow.TransientValueData; import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil; import org.exoplatform.services.jcr.impl.util.ISO9075; import org.exoplatform.services.jcr.impl.util.StringConverter; import org.exoplatform.services.jcr.impl.xml.importing.dataflow.ImportNodeData; import org.exoplatform.services.jcr.impl.xml.importing.dataflow.ImportPropertyData; import org.exoplatform.services.jcr.util.IdGenerator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.security.ConversationState; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.jcr.NamespaceRegistry; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; /** * Created by The eXo Platform SAS. * * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a> * @version $Id: DocumentViewImporter.java 14221 2008-05-14 08:27:41Z ksm $ */ public class DocumentViewImporter extends BaseXmlImporter { /** * */ private static Log log = ExoLogger.getLogger("exo.jcr.component.core.DocumentViewImporter"); /** * */ private ImportPropertyData xmlCharactersProperty; /** * */ private String xmlCharactersPropertyValue; /** * DocumentViewImporter constructor. * * @param parent NodeData, parent node * @param ancestorToSave QPath * @param uuidBehavior int * @param dataConsumer ItemDataConsumer * @param ntManager NodeTypeDataManager * @param locationFactory LocationFactory * @param valueFactory ValueFactoryImpl * @param namespaceRegistry NamespaceRegistry * @param accessManager AccessManager * @param userState ConversationState * @param context Map * @param repository RepositoryImpl * @param currentWorkspaceName String */ public DocumentViewImporter(NodeData parent, QPath ancestorToSave, int uuidBehavior, ItemDataConsumer dataConsumer, NodeTypeDataManager ntManager, LocationFactory locationFactory, ValueFactoryImpl valueFactory, NamespaceRegistry namespaceRegistry, AccessManager accessManager, ConversationState userState, Map<String, Object> context, RepositoryImpl repository, String currentWorkspaceName) { super(parent, ancestorToSave, uuidBehavior, dataConsumer, ntManager, locationFactory, valueFactory, namespaceRegistry, accessManager, userState, context, repository, currentWorkspaceName); xmlCharactersProperty = null; xmlCharactersPropertyValue = null; } /** * {@inheritDoc} */ public void characters(char[] ch, int start, int length) throws RepositoryException { StringBuilder text = new StringBuilder(); text.append(ch, start, length); if (log.isDebugEnabled()) { log.debug("Property:xmltext=" + text + " Parent=" + getParent().getQPath().getAsString()); } if (xmlCharactersProperty != null) { xmlCharactersPropertyValue += text.toString(); xmlCharactersProperty.setValue(new TransientValueData(xmlCharactersPropertyValue)); } else { ImportNodeData nodeData = ImportNodeData.createNodeData(getParent(), Constants.JCR_XMLTEXT, Constants.NT_UNSTRUCTURED, getNodeIndex(getParent(), Constants.JCR_XMLTEXT, null), getNextChildOrderNum(getParent())); changesLog.add(new ItemState(nodeData, ItemState.ADDED, true, getAncestorToSave())); if (log.isDebugEnabled()) { log.debug("New node " + nodeData.getQPath().getAsString()); } ImportPropertyData newProperty = new ImportPropertyData(QPath.makeChildPath(nodeData.getQPath(), Constants.JCR_PRIMARYTYPE), IdGenerator.generate(), -1, PropertyType.NAME, nodeData.getIdentifier(), false); newProperty.setValue(new TransientValueData(Constants.NT_UNSTRUCTURED)); changesLog.add(new ItemState(newProperty, ItemState.ADDED, true, getAncestorToSave())); newProperty = new ImportPropertyData(QPath.makeChildPath(nodeData.getQPath(), Constants.JCR_XMLCHARACTERS), IdGenerator.generate(), -1, PropertyType.STRING, nodeData.getIdentifier(), false); newProperty.setValue(new TransientValueData(text.toString())); changesLog.add(new ItemState(newProperty, ItemState.ADDED, true, getAncestorToSave())); xmlCharactersProperty = newProperty; xmlCharactersPropertyValue = text.toString(); } } /** * {@inheritDoc} */ public void endElement(String uri, String localName, String qName) throws RepositoryException { tree.pop(); xmlCharactersProperty = null; } public void startElement(String namespaceURI, String localName, String qName, Map<String, String> atts) throws RepositoryException { String nodeName = ISO9075.decode(qName); if ("jcr:root".equals(nodeName)) { nodeName = ""; } xmlCharactersProperty = null; List<NodeTypeData> nodeTypes = new ArrayList<NodeTypeData>(); HashMap<InternalQName, String> propertiesMap = new HashMap<InternalQName, String>(); List<InternalQName> mixinNodeTypes = new ArrayList<InternalQName>(); InternalQName jcrName = locationFactory.parseJCRName(nodeName).getInternalName(); parseAttr(atts, nodeTypes, mixinNodeTypes, propertiesMap, jcrName); ImportNodeData nodeData = createNode(nodeTypes, propertiesMap, mixinNodeTypes, jcrName); NodeData parentNodeData = getParent(); changesLog.add(new ItemState(nodeData, ItemState.ADDED, true, getAncestorToSave())); tree.push(nodeData); if (log.isDebugEnabled()) { log.debug("Node " + ": " + nodeData.getQPath().getAsString()); } Iterator<InternalQName> keys = propertiesMap.keySet().iterator(); PropertyData newProperty; while (keys.hasNext()) { newProperty = null; InternalQName propName = keys.next(); if (log.isDebugEnabled()) { log.debug("Property NAME: " + propName + "=" + propertiesMap.get(propName)); } if (propName.equals(Constants.JCR_PRIMARYTYPE)) { InternalQName childName = locationFactory.parseJCRName(propertiesMap.get(Constants.JCR_PRIMARYTYPE)).getInternalName(); if (!nodeTypeDataManager.isChildNodePrimaryTypeAllowed(nodeData.getQName(), childName, parentNodeData.getPrimaryTypeName(), parentNodeData.getMixinTypeNames())) { throw new ConstraintViolationException("Can't add node " + nodeData.getQName().getAsString() + " to " + parentNodeData.getQPath().getAsString() + " node type " + propertiesMap.get(Constants.JCR_PRIMARYTYPE) + " is not allowed as child's node type for parent node type " + parentNodeData.getPrimaryTypeName().getAsString()); } newProperty = endPrimaryType(nodeData.getPrimaryTypeName()); } else if (propName.equals(Constants.JCR_MIXINTYPES)) { newProperty = endMixinTypes(mixinNodeTypes, propName); } else if (nodeData.isMixReferenceable() && propName.equals(Constants.JCR_UUID)) { newProperty = endUuid(nodeData, propName, propertiesMap.get(Constants.JCR_UUID)); } else { PropertyDefinitionData pDef; PropertyDefinitionDatas defs; InternalQName[] nTypes = mixinNodeTypes.toArray(new InternalQName[mixinNodeTypes.size() + 1]); nTypes[nTypes.length - 1] = nodeData.getPrimaryTypeName(); defs = nodeTypeDataManager.getPropertyDefinitions(propName, nTypes); if (defs == null || defs.getAnyDefinition() == null) { if (!((Boolean)context.get(ContentImporter.RESPECT_PROPERTY_DEFINITIONS_CONSTRAINTS))) { log.warn("Property definition not found for " + propName.getAsString()); continue; } throw new RepositoryException("Property definition not found for " + propName.getAsString()); } pDef = defs.getAnyDefinition(); if ((pDef == null) || (defs == null)) { throw new RepositoryException("no propertyDefinition found"); } if (pDef.getRequiredType() == PropertyType.BINARY) { newProperty = endBinary(propertiesMap, newProperty, propName); } else { StringTokenizer spaceTokenizer = new StringTokenizer(propertiesMap.get(propName)); List<ValueData> values = new ArrayList<ValueData>(); int pType = pDef.getRequiredType() > 0 ? pDef.getRequiredType() : PropertyType.STRING; if (defs.getAnyDefinition().isResidualSet()) { if (nodeData.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)) { if (nodeData.getPrimaryTypeName().equals(Constants.NT_FROZENNODE)) { // get primaryType InternalQName fptName = locationFactory.parseJCRName(atts.get("jcr:frozenPrimaryType")).getInternalName(); // get mixin types List<JCRName> mtNames = getJCRNames(atts.get("jcr:frozenMixinTypes")); InternalQName fmtName[] = new InternalQName[mtNames.size()]; for (int i = 0; i < mtNames.size(); i++) { fmtName[i] = new InternalQName(mtNames.get(i).getNamespace(), mtNames.get(i).getName()); } PropertyDefinitionDatas ptVhdefs = nodeTypeDataManager.getPropertyDefinitions(propName, fptName, fmtName); if (ptVhdefs != null) { pType = (ptVhdefs.getAnyDefinition().getRequiredType() > 0 ? ptVhdefs.getAnyDefinition() .getRequiredType() : PropertyType.STRING); } } } } if ("".equals(propertiesMap.get(propName))) { // Skip empty non string values if (pType != PropertyType.STRING) { continue; } String denormalizeString = StringConverter.denormalizeString(propertiesMap.get(propName)); Value value = valueFactory.createValue(denormalizeString, pType); values.add(((BaseValue)value).getInternalData()); if (Constants.EXO_OWNER.equals(propName)) { nodeData.setExoOwner(denormalizeString); } } else { List<String> denormalizeStrings = new ArrayList<String>(); while (spaceTokenizer.hasMoreTokens()) { String elem = spaceTokenizer.nextToken(); String denormalizeString = StringConverter.denormalizeString(elem); denormalizeStrings.add(denormalizeString); Value value = valueFactory.createValue(denormalizeString, pType); if (log.isDebugEnabled()) { String valueAsString = null; try { valueAsString = value.getString(); } catch (Exception e) { log.error("Can't present value as string. " + e.getMessage()); valueAsString = "[Can't present value as string]"; } log.debug("Property " + ExtendedPropertyType.nameFromValue(pType) + ": " + propName + "=" + valueAsString); } values.add(((BaseValue)value).getInternalData()); } if (pType == ExtendedPropertyType.PERMISSION) { nodeData.setExoPrivileges(denormalizeStrings); } else if (Constants.EXO_OWNER.equals(propName)) { nodeData.setExoOwner(denormalizeStrings.get(0)); } } boolean isMultivalue = true; // determinating is property multivalue; if (values.size() == 1) { PropertyDefinitionDatas vhdefs = null; if (defs.getAnyDefinition().isResidualSet()) { if (nodeData.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)) { if (nodeData.getPrimaryTypeName().equals(Constants.NT_FROZENNODE)) { // get primaryType InternalQName fptName = locationFactory.parseJCRName(atts.get("jcr:frozenPrimaryType")).getInternalName(); // get mixin types List<JCRName> mtNames = getJCRNames(atts.get("jcr:frozenMixinTypes")); InternalQName fmtName[] = new InternalQName[mtNames.size()]; for (int i = 0; i < mtNames.size(); i++) { fmtName[i] = new InternalQName(mtNames.get(i).getNamespace(), mtNames.get(i).getName()); } vhdefs = nodeTypeDataManager.getPropertyDefinitions(propName, fptName, fmtName); if (vhdefs != null) { isMultivalue = (vhdefs.getDefinition(true) != null ? true : false); } } } } // there is single-value defeniton if (vhdefs == null && defs.getDefinition(false) != null) { isMultivalue = false; } } else { if ((defs.getDefinition(true) == null) && (defs.getDefinition(false) != null)) { throw new ValueFormatException("Can not assign multiple-values Value" + " to a single-valued property " + propName.getAsString() + " node " + jcrName.getName()); } } newProperty = ImportPropertyData.createPropertyData(getParent(), propName, pType, isMultivalue, values); if (nodeData.isMixVersionable()) { endVersionable(nodeData, values, propName); } } } // skip versionable if ((newProperty.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH) || (!Constants.JCR_VERSIONHISTORY .equals(propName) && !Constants.JCR_BASEVERSION.equals(propName) && !Constants.JCR_PREDECESSORS.equals(propName)))) { changesLog.add(new ItemState(newProperty, ItemState.ADDED, true, getAncestorToSave())); } } nodeData.setACL(ACLInitializationHelper.initAcl(parentNodeData.getACL(), nodeData.getExoOwner(), nodeData.getExoPrivileges())); if (nodeData.isMixVersionable()) { createVersionHistory(nodeData); } } private List<JCRName> getJCRNames(String string) throws RepositoryException { List<JCRName> mtNames = new ArrayList<JCRName>(); StringTokenizer spaceToken = new StringTokenizer(string); List<String> denormalizedStrings = new ArrayList<String>(); while (spaceToken.hasMoreTokens()) { String elem = spaceToken.nextToken(); String denormalizeString = StringConverter.denormalizeString(elem); denormalizedStrings.add(denormalizeString); } for (String mixinName : denormalizedStrings) { mtNames.add(locationFactory.parseJCRName(mixinName)); } return mtNames; } private ImportNodeData createNode(List<NodeTypeData> nodeTypes, HashMap<InternalQName, String> propertiesMap, List<InternalQName> mixinNodeTypes, InternalQName jcrName) throws PathNotFoundException, IllegalPathException, RepositoryException { ImportNodeData nodeData = new ImportNodeData(getParent(), jcrName, getNodeIndex(getParent(), jcrName, null)); InternalQName[] allNodeTypes = new InternalQName[nodeTypes.size() + mixinNodeTypes.size()]; for (int i = 0; i < nodeTypes.size(); i++) { allNodeTypes[i] = nodeTypes.get(i).getName(); } for (int i = 0; i < mixinNodeTypes.size(); i++) { allNodeTypes[nodeTypes.size() + i] = mixinNodeTypes.get(i); } nodeData.setPrimaryTypeName(locationFactory.parseJCRName(propertiesMap.get(Constants.JCR_PRIMARYTYPE)) .getInternalName()); nodeData.setOrderNumber(getNextChildOrderNum(getParent())); nodeData.setMixinTypeNames(mixinNodeTypes.toArray(new InternalQName[mixinNodeTypes.size()])); nodeData.setMixReferenceable(nodeTypeDataManager.isNodeType(Constants.MIX_REFERENCEABLE, allNodeTypes)); nodeData.setIdentifier(IdGenerator.generate()); if (nodeData.isMixReferenceable()) { nodeData.setMixVersionable(nodeTypeDataManager.isNodeType(Constants.MIX_VERSIONABLE, allNodeTypes)); checkReferenceable(nodeData, propertiesMap.get(Constants.JCR_UUID)); } return nodeData; } private PropertyData endBinary(HashMap<InternalQName, String> propertiesMap, PropertyData newProperty, InternalQName propName) throws RepositoryException { try { InputStream vStream = new ByteArrayInputStream(Base64.decode(propertiesMap.get(propName))); TransientValueData binaryValue = new TransientValueData(0, vStream, null, valueFactory.getSpoolConfig()); binaryValue.getAsStream().close(); newProperty = ImportPropertyData.createPropertyData(getParent(), propName, PropertyType.BINARY, false, binaryValue); } catch (DecodingException e) { throw new RepositoryException(e); } catch (IOException e) { throw new RepositoryException(e); } return newProperty; } private PropertyData endMixinTypes(List<InternalQName> mixinNodeTypes, InternalQName key) { PropertyData newProperty; List<ValueData> valuesData = new ArrayList<ValueData>(mixinNodeTypes.size()); for (InternalQName mixinQname : mixinNodeTypes) { valuesData.add(new TransientValueData(mixinQname)); } newProperty = ImportPropertyData.createPropertyData(getParent(), key, PropertyType.NAME, true, valuesData); return newProperty; } private PropertyData endPrimaryType(InternalQName primaryTypeName) { PropertyData newProperty; if (log.isDebugEnabled()) { log.debug("Property NAME: " + primaryTypeName); } newProperty = ImportPropertyData.createPropertyData(getParent(), Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false, new TransientValueData(primaryTypeName)); return newProperty; } private PropertyData endUuid(ImportNodeData nodeData, InternalQName key, String properyValueUUID) throws ValueFormatException, UnsupportedRepositoryOperationException, RepositoryException, IllegalStateException { PropertyData newProperty; if (nodeData.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)) { newProperty = ImportPropertyData.createPropertyData(getParent(), Constants.JCR_UUID, PropertyType.STRING, false, new TransientValueData(properyValueUUID)); } else { newProperty = ImportPropertyData.createPropertyData(getParent(), Constants.JCR_UUID, PropertyType.STRING, false, new TransientValueData(nodeData.getIdentifier())); } return newProperty; } private void endVersionable(ImportNodeData nodeData, List<ValueData> values, InternalQName propName) throws RepositoryException { { if (propName.equals(Constants.JCR_VERSIONHISTORY)) { nodeData.setVersionHistoryIdentifier(ValueDataUtil.getString(values.get(0))); // check if node contains VH if (dataConsumer.getItemData(nodeData.getVersionHistoryIdentifier()) != null) { ItemState vhLastState = getLastItemState(nodeData.getVersionHistoryIdentifier()); nodeData.setContainsVersionhistory(vhLastState == null || !vhLastState.isDeleted()); } else { nodeData.setContainsVersionhistory(false); } } else if (propName.equals(Constants.JCR_BASEVERSION)) { nodeData.setBaseVersionIdentifier(ValueDataUtil.getString(values.get(0))); } } } private void parseAttr(Map<String, String> atts, List<NodeTypeData> nodeTypes, List<InternalQName> mixinNodeTypes, HashMap<InternalQName, String> props, InternalQName nodeName) throws PathNotFoundException, RepositoryException { // default primary type if (atts != null && !atts.containsKey("jcr:primaryType")) { NodeData parent = getParent(); NodeDefinitionData nodeNt = nodeTypeDataManager.getChildNodeDefinition(nodeName, parent.getPrimaryTypeName(), parent .getMixinTypeNames()); NodeTypeData nodeType; if (nodeNt.getName().equals(Constants.JCR_ANY_NAME) && nodeNt.getDefaultPrimaryType() != null) { nodeType = nodeTypeDataManager.getNodeType(nodeNt.getDefaultPrimaryType()); } else { nodeType = nodeTypeDataManager.getNodeType(nodeNt.getName()); } if (nodeType == null) throw new ConstraintViolationException("Can not define node-type for node " + nodeName.getAsString() + ", parent node type " + parent.getPrimaryTypeName().getAsString()); nodeTypes.add(nodeType); props.put(Constants.JCR_PRIMARYTYPE, locationFactory.createJCRName(nodeType.getName()).getAsString()); } if (atts != null) { for (String key : atts.keySet()) { String attValue = atts.get(key); String propName = ISO9075.decode(key); if (log.isDebugEnabled()) { log.debug(propName + ":" + attValue); } InternalQName propInternalQName = locationFactory.parseJCRName(propName).getInternalName(); if (Constants.JCR_PRIMARYTYPE.equals(propInternalQName)) { String primaryNodeType = StringConverter.denormalizeString(attValue); InternalQName ntName = locationFactory.parseJCRName(primaryNodeType).getInternalName(); NodeTypeData nodeType = nodeTypeDataManager.getNodeType(ntName); if (nodeType == null) throw new ConstraintViolationException("Can not find node type " + primaryNodeType); nodeTypes.add(nodeType); props.put(propInternalQName, primaryNodeType); } else if (Constants.JCR_MIXINTYPES.equals(propInternalQName)) { String[] amTypes = attValue.split(" "); for (int mi = 0; mi < amTypes.length; mi++) { amTypes[mi] = StringConverter.denormalizeString(amTypes[mi]); InternalQName name = locationFactory.parseJCRName(amTypes[mi]).getInternalName(); mixinNodeTypes.add(name); NodeTypeData nodeType = nodeTypeDataManager.getNodeType(name); if (nodeType == null) throw new ConstraintViolationException("Can not find node type " + amTypes[mi]); nodeTypes.add(nodeType); } // value will not be used anywhere; for key only props.put(propInternalQName, null); } else { props.put(propInternalQName, attValue); } } } } }