/*
* 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.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.core.ExtendedPropertyType;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
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.IllegalNameException;
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.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.ValueDataConvertor;
import org.exoplatform.services.jcr.impl.xml.DecodedValue;
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.impl.xml.importing.dataflow.PropertyInfo;
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.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.NamespaceRegistry;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
* @version $Id: SystemViewImporter.java 34801 2009-07-31 15:44:50Z dkatayev $
*/
public class SystemViewImporter extends BaseXmlImporter
{
/**
*
*/
private static Log log = ExoLogger.getLogger("exo.jcr.component.core.SystemViewImporter");
protected PropertyInfo propertyInfo = new PropertyInfo();
protected Map<String, NodePropertiesInfo> mapNodePropertiesInfo = new HashMap<String, NodePropertiesInfo>();
/**
* Root node name.
*/
protected String ROOT_NODE_NAME = "jcr:root";
/**
* @param parent
* @param uuidBehavior
* @param saveType
* @param respectPropertyDefinitionsConstraints
*/
public SystemViewImporter(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);
}
/**
* {@inheritDoc}
*/
public void characters(char[] ch, int start, int length) throws RepositoryException
{
// property values
if (propertyInfo.getValues().size() > 0)
{
DecodedValue curPropValue = propertyInfo.getValues().get(propertyInfo.getValues().size() - 1);
if (curPropValue.isComplete())
{
return;
}
if (propertyInfo.getType() == PropertyType.BINARY)
{
try
{
curPropValue.getBinaryDecoder().write(ch, start, length);
}
catch (IOException e)
{
throw new RepositoryException(e);
}
}
else
{
curPropValue.getStringBuffer().append(ch, start, length);
}
}
else
{
log.debug("Wrong XML content. Element 'sv:value' expected,"
+ " but SAX event 'characters' occured. characters:[" + new String(ch, start, length) + "]");
}
}
/**
* {@inheritDoc}
*/
public void endElement(String uri, String localName, String name) throws RepositoryException
{
InternalQName elementName = locationFactory.parseJCRName(name).getInternalName();
if (Constants.SV_NODE_NAME.equals(elementName))
{
// sv:node element
endNode();
}
else if (Constants.SV_PROPERTY_NAME.equals(elementName))
{
// sv:property element
ImportPropertyData propertyData = endProperty();
if (propertyData != null)
{
changesLog.add(new ItemState(propertyData, ItemState.ADDED, true, getAncestorToSave()));
ImportNodeData currentNodeInfo = (ImportNodeData)getParent();
NodePropertiesInfo currentNodePropertiesInfo = mapNodePropertiesInfo.get(currentNodeInfo.getQPath().getAsString());
currentNodePropertiesInfo.addProperty(propertyData);
}
}
else if (Constants.SV_VALUE_NAME.equals(elementName))
{
// sv:value element
//mark current value as completed
DecodedValue curPropValue = propertyInfo.getValues().get(propertyInfo.getValues().size() - 1);
curPropValue.setComplete(true);
}
else
{
throw new RepositoryException("invalid element in system view xml document: " + localName);
}
}
/**
* {@inheritDoc}
*/
public void startElement(String namespaceURI, String localName, String name, Map<String, String> atts)
throws RepositoryException
{
InternalQName elementName = locationFactory.parseJCRName(name).getInternalName();
if (Constants.SV_NODE_NAME.equals(elementName))
{
// sv:node element
// node name (value of sv:name attribute)
String svName = getAttribute(atts, Constants.SV_NAME_NAME);
if (svName == null)
{
throw new RepositoryException("Missing mandatory sv:name attribute of element sv:node");
}
NodeData parentData = null;
parentData = getParent();
InternalQName currentNodeName = null;
if (ROOT_NODE_NAME.equals(svName))
{
currentNodeName = Constants.ROOT_PATH.getName();
}
else
{
currentNodeName = locationFactory.parseJCRName(svName).getInternalName();
}
int nodeIndex = getNodeIndex(parentData, currentNodeName, null);
ImportNodeData newNodeData = new ImportNodeData(parentData, currentNodeName, nodeIndex);
// preset of ACL
newNodeData.setACL(parentData.getACL());
newNodeData.setOrderNumber(getNextChildOrderNum(parentData));
newNodeData.setIdentifier(IdGenerator.generate());
changesLog.add(new ItemState(newNodeData, ItemState.ADDED, true, getAncestorToSave()));
mapNodePropertiesInfo.put(newNodeData.getQPath().getAsString(), new NodePropertiesInfo(newNodeData));
tree.push(newNodeData);
}
else if (Constants.SV_PROPERTY_NAME.equals(elementName))
{
// sv:property element
propertyInfo.setValues(new ArrayList<DecodedValue>());
// property name (value of sv:name attribute)
String svName = getAttribute(atts, Constants.SV_NAME_NAME);
if (svName == null)
{
throw new RepositoryException("missing mandatory sv:name attribute of element sv:property");
}
propertyInfo.setName(locationFactory.parseJCRName(svName).getInternalName());
propertyInfo.setIndentifer(IdGenerator.generate());
// property type (sv:type attribute)
String type = getAttribute(atts, Constants.SV_TYPE_NAME);
if (type == null)
{
throw new RepositoryException("missing mandatory sv:type attribute of element sv:property");
}
try
{
propertyInfo.setType(ExtendedPropertyType.valueFromName(type));
}
catch (IllegalArgumentException e)
{
throw new RepositoryException("Unknown property type: " + type, e);
}
}
else if (Constants.SV_VALUE_NAME.equals(elementName))
{
// sv:value element
propertyInfo.getValues().add(new DecodedValue());
}
else
{
throw new RepositoryException("Unknown element " + elementName.getAsString());
}
}
/**
* @return
* @throws PathNotFoundException
* @throws RepositoryException
* @throws NoSuchNodeTypeException
*/
private ImportPropertyData endMixinTypes() throws PathNotFoundException, RepositoryException,
NoSuchNodeTypeException
{
ImportPropertyData propertyData;
InternalQName[] mixinNames = new InternalQName[propertyInfo.getValuesSize()];
List<ValueData> values = new ArrayList<ValueData>(propertyInfo.getValuesSize());
ImportNodeData currentNodeInfo = (ImportNodeData)getParent();
for (int i = 0; i < propertyInfo.getValuesSize(); i++)
{
String value = propertyInfo.getValues().get(i).toString();
mixinNames[i] = locationFactory.parseJCRName(value).getInternalName();
currentNodeInfo.addNodeType((nodeTypeDataManager.getNodeType(mixinNames[i])));
values.add(new TransientValueData(value.toString()));
}
currentNodeInfo.setMixinTypeNames(mixinNames);
propertyData =
new ImportPropertyData(QPath.makeChildPath(currentNodeInfo.getQPath(), propertyInfo.getName()), propertyInfo
.getIndentifer(), 0, propertyInfo.getType(), currentNodeInfo.getIdentifier(), true);
propertyData.setValues(parseValues());
return propertyData;
}
/**
* endNode.
*
* @throws RepositoryException
*/
private void endNode() throws RepositoryException
{
ImportNodeData currentNodeInfo = (ImportNodeData)tree.pop();
NodePropertiesInfo currentNodePropertiesInfo = mapNodePropertiesInfo.get(currentNodeInfo.getQPath().getAsString());
if (currentNodePropertiesInfo != null)
{
checkProperties(currentNodePropertiesInfo);
}
mapNodePropertiesInfo.remove(currentNodeInfo.getQPath().getAsString());
currentNodeInfo.setMixinTypeNames(currentNodeInfo.getMixinTypeNames());
if (currentNodeInfo.isMixVersionable())
{
createVersionHistory(currentNodeInfo);
}
currentNodeInfo.setACL(initAcl(currentNodeInfo.getACL(), currentNodeInfo.isExoOwneable(), currentNodeInfo
.isExoPrivilegeable(), currentNodeInfo.getExoOwner(), currentNodeInfo.getExoPrivileges()));
}
/**
* Checking priopertis if nodetype is nt:frozennode
*
* @param currentNodePropertiesInfo
* @throws RepositoryException
* @throws IOException
* @throws IllegalNameException
* @throws IllegalStateException
*/
private void checkProperties(NodePropertiesInfo currentNodePropertiesInfo) throws RepositoryException
{
if (currentNodePropertiesInfo.getNode().getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)
&& currentNodePropertiesInfo.getNode().getPrimaryTypeName().equals(Constants.NT_FROZENNODE))
{
InternalQName fptName = null;
List<InternalQName> fmtNames = new ArrayList<InternalQName>();
// get frozenPrimaryType and frozenMixinTypes
try
{
for (ImportPropertyData propertyData : currentNodePropertiesInfo.getProperties())
{
if (propertyData.getQName().equals(Constants.JCR_FROZENPRIMARYTYPE))
{
fptName = InternalQName.parse(new String(propertyData.getValues().get(0).getAsByteArray(), Constants.DEFAULT_ENCODING));
}
else if (propertyData.getQName().equals(Constants.JCR_FROZENMIXINTYPES))
{
for (ValueData valueData : propertyData.getValues())
{
fmtNames.add(InternalQName.parse(new String(valueData.getAsByteArray(), Constants.DEFAULT_ENCODING)));
}
}
}
}
catch (IllegalStateException e)
{
throw new RepositoryException(e.getMessage(), e);
}
catch (IllegalNameException e)
{
throw new RepositoryException(e.getMessage(), e);
}
catch (IOException e)
{
throw new RepositoryException(e.getMessage(), e);
}
InternalQName nodePrimaryTypeName = currentNodePropertiesInfo.getNode().getPrimaryTypeName();
InternalQName[] nodeMixinTypeName = currentNodePropertiesInfo.getNode().getMixinTypeNames();
for (ImportPropertyData propertyData : currentNodePropertiesInfo.getProperties())
{
PropertyDefinitionDatas defs = nodeTypeDataManager.getPropertyDefinitions(propertyData.getQName(), nodePrimaryTypeName, nodeMixinTypeName);
if (defs == null || (defs != null && defs.getAnyDefinition().isResidualSet()))
{
PropertyDefinitionDatas vhdefs =
nodeTypeDataManager.getPropertyDefinitions(propertyData.getQName(), fptName, fmtNames
.toArray(new InternalQName[fmtNames.size()]));
if (vhdefs != null)
{
boolean isMultivalue = (vhdefs.getDefinition(true) != null ? true : false);
propertyData.setMultivalue(isMultivalue);
}
}
}
}
}
/**
* endPrimaryType.
*
* @return
* @throws PathNotFoundException
* @throws RepositoryException
* @throws NoSuchNodeTypeException
*/
private ImportPropertyData endPrimaryType() throws PathNotFoundException, RepositoryException,
NoSuchNodeTypeException
{
ImportPropertyData propertyData;
String sName = propertyInfo.getValues().get(0).toString();
InternalQName primaryTypeName = locationFactory.parseJCRName(sName).getInternalName();
ImportNodeData nodeData = (ImportNodeData)tree.pop();
if (!Constants.ROOT_UUID.equals(nodeData.getIdentifier()))
{
NodeData parentNodeData = getParent();
// nodeTypeDataManager.findChildNodeDefinition(primaryTypeName,)
if (!nodeTypeDataManager.isChildNodePrimaryTypeAllowed(primaryTypeName, parentNodeData.getPrimaryTypeName(),
parentNodeData.getMixinTypeNames()))
{
throw new ConstraintViolationException("Can't add node " + nodeData.getQName().getAsString() + " to "
+ parentNodeData.getQPath().getAsString() + " node type " + sName
+ " is not allowed as child's node type for parent node type "
+ parentNodeData.getPrimaryTypeName().getAsString());
}
}
//
nodeData.addNodeType((nodeTypeDataManager.getNodeType(primaryTypeName)));
nodeData.setPrimaryTypeName(primaryTypeName);
propertyData =
new ImportPropertyData(QPath.makeChildPath(nodeData.getQPath(), propertyInfo.getName()), propertyInfo
.getIndentifer(), 0, propertyInfo.getType(), nodeData.getIdentifier(), false);
propertyData.setValues(parseValues());
tree.push(nodeData);
return propertyData;
}
/**
* @return
* @throws PathNotFoundException
* @throws RepositoryException
* @throws NoSuchNodeTypeException
* @throws IllegalPathException
* @throws ValueFormatException
*/
private ImportPropertyData endProperty() throws PathNotFoundException, RepositoryException, NoSuchNodeTypeException,
IllegalPathException, ValueFormatException
{
ImportPropertyData propertyData = null;
if (Constants.JCR_PRIMARYTYPE.equals(propertyInfo.getName()))
{
propertyData = endPrimaryType();
}
else if (Constants.JCR_MIXINTYPES.equals(propertyInfo.getName()))
{
propertyData = endMixinTypes();
}
else if (Constants.JCR_UUID.equals(propertyInfo.getName()))
{
propertyData = endUuid();
// skip verionable properties
}
else if (!getParent().getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)
&& (Constants.JCR_VERSIONHISTORY.equals(propertyInfo.getName())
|| Constants.JCR_BASEVERSION.equals(propertyInfo.getName()) || Constants.JCR_PREDECESSORS
.equals(propertyInfo.getName())))
{
propertyData = null;
endVersionable((ImportNodeData)getParent(), parseValues());
}
else
{
ImportNodeData currentNodeInfo = (ImportNodeData)getParent();
List<ValueData> values = parseValues();
// determinating is property multivalue;
boolean isMultivalue = true;
PropertyDefinitionDatas defs =
nodeTypeDataManager.getPropertyDefinitions(propertyInfo.getName(), currentNodeInfo.getPrimaryTypeName(),
currentNodeInfo.getMixinTypeNames());
if (defs == null)
{
if (!((Boolean)context.get(ContentImporter.RESPECT_PROPERTY_DEFINITIONS_CONSTRAINTS)))
{
log.warn("Property definition not found for " + propertyInfo.getName());
return null;
}
else
throw new RepositoryException("Property definition not found for " + propertyInfo.getName());
}
if (values.size() == 1)
{
// there is single-value defeniton
if (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 "
+ propertyInfo.getName().getName());
}
}
log.debug("Import " + propertyInfo.getName().getName() + " size=" + propertyInfo.getValuesSize()
+ " isMultivalue=" + isMultivalue);
propertyData =
new ImportPropertyData(QPath.makeChildPath(currentNodeInfo.getQPath(), propertyInfo.getName()),
propertyInfo.getIndentifer(), 0, propertyInfo.getType(), currentNodeInfo.getIdentifier(), isMultivalue);
propertyData.setValues(values);
}
return propertyData;
}
/**
* @return
* @throws RepositoryException
* @throws PathNotFoundException
* @throws IllegalPathException
*/
private ImportPropertyData endUuid() throws RepositoryException, PathNotFoundException, IllegalPathException
{
ImportPropertyData propertyData;
ImportNodeData currentNodeInfo = (ImportNodeData)tree.pop();
currentNodeInfo.setMixReferenceable(nodeTypeDataManager.isNodeType(Constants.MIX_REFERENCEABLE, currentNodeInfo
.getPrimaryTypeName(), currentNodeInfo.getMixinTypeNames()));
if (currentNodeInfo.isMixReferenceable())
{
currentNodeInfo.setMixVersionable(nodeTypeDataManager.isNodeType(Constants.MIX_VERSIONABLE, currentNodeInfo
.getPrimaryTypeName(), currentNodeInfo.getMixinTypeNames()));
checkReferenceable(currentNodeInfo, propertyInfo.getValues().get(0).toString());
}
propertyData =
new ImportPropertyData(QPath.makeChildPath(currentNodeInfo.getQPath(), propertyInfo.getName()), propertyInfo
.getIndentifer(), 0, propertyInfo.getType(), currentNodeInfo.getIdentifier(), false);
if (currentNodeInfo.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH))
{
propertyData.setValue(new TransientValueData(propertyInfo.getValues().get(0).toString()));
}
else
{
propertyData.setValue(new TransientValueData(currentNodeInfo.getIdentifier()));
}
tree.push(currentNodeInfo);
mapNodePropertiesInfo.put(currentNodeInfo.getQPath().getAsString(), new NodePropertiesInfo(currentNodeInfo));
return propertyData;
}
/**
* @param currentNodeInfo
* @param values
* @throws RepositoryException
*/
private void endVersionable(ImportNodeData currentNodeInfo, List<ValueData> values) throws RepositoryException
{
try
{
if (propertyInfo.getName().equals(Constants.JCR_VERSIONHISTORY))
{
String versionHistoryIdentifier = null;
versionHistoryIdentifier = ValueDataConvertor.readString(values.get(0));
currentNodeInfo.setVersionHistoryIdentifier(versionHistoryIdentifier);
currentNodeInfo.setContainsVersionhistory(dataConsumer.getItemData(versionHistoryIdentifier) != null);
}
else if (propertyInfo.getName().equals(Constants.JCR_BASEVERSION))
{
currentNodeInfo.setBaseVersionIdentifier(ValueDataConvertor.readString(values.get(0)));
}
}
catch (IOException e)
{
throw new RepositoryException(e);
}
}
/**
* Returns the list of ValueData for current property
*
* @return
* @throws RepositoryException
*/
private List<ValueData> parseValues() throws RepositoryException
{
List<ValueData> values = new ArrayList<ValueData>(propertyInfo.getValuesSize());
List<String> stringValues = new ArrayList<String>();
for (int k = 0; k < propertyInfo.getValuesSize(); k++)
{
if (propertyInfo.getType() == PropertyType.BINARY)
{
try
{
InputStream vStream = propertyInfo.getValues().get(k).getInputStream();
// TODO cleanup
// TransientValueData binaryValue = new TransientValueData(vStream);
TransientValueData binaryValue =
new TransientValueData(k, null, vStream, null, valueFactory.getFileCleaner(), valueFactory
.getMaxBufferSize(), null, true);
// Call to spool file into tmp
binaryValue.getAsStream().close();
vStream.close();
propertyInfo.getValues().get(k).remove();
values.add(binaryValue);
}
catch (IOException e)
{
throw new RepositoryException(e);
}
}
else
{
String val = new String(propertyInfo.getValues().get(k).toString());
stringValues.add(val);
values.add(((BaseValue)valueFactory.createValue(val, propertyInfo.getType())).getInternalData());
}
}
if (propertyInfo.getType() == ExtendedPropertyType.PERMISSION)
{
ImportNodeData currentNodeInfo = (ImportNodeData)getParent();
currentNodeInfo.setExoPrivileges(stringValues);
}
else if (Constants.EXO_OWNER.equals(propertyInfo.getName()))
{
ImportNodeData currentNodeInfo = (ImportNodeData)getParent();
currentNodeInfo.setExoOwner(stringValues.get(0));
}
return values;
}
/**
* Returns the value of the named XML attribute.
*
* @param attributes set of XML attributes
* @param name attribute name
* @return attribute value, or <code>null</code> if the named attribute is not
* found
* @throws RepositoryException
*/
protected String getAttribute(Map<String, String> attributes, InternalQName name) throws RepositoryException
{
JCRName jname = locationFactory.createJCRName(name);
return attributes.get(jname.getAsString());
}
}