/*
* 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.exporting;
import org.apache.ws.commons.util.Base64;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemDataTraversingVisitor;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.ItemData;
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.value.ValueFactoryImpl;
import org.exoplatform.services.jcr.impl.dataflow.NodeDataOrderComparator;
import org.exoplatform.services.jcr.impl.dataflow.PropertyDataOrderComparator;
import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil;
import org.exoplatform.services.jcr.impl.util.ISO9075;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.jcr.NamespaceRegistry;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFormatException;
import javax.xml.stream.XMLStreamException;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
* @version $Id: ImportNodeData.java 11907 2008-03-13 15:36:21Z ksm $
*/
public abstract class BaseXmlExporter extends ItemDataTraversingVisitor
{
/**
* Empty namespace prefix.
*/
public static final String DEFAULT_EMPTY_NAMESPACE_PREFIX = "jcr_default_empty" + "_namespace_prefix";
/**
* Multi-value delimiter.
*/
public static final String MULTI_VALUE_DELIMITER = " ";
/**
* Root node name.
*/
protected static final String JCR_ROOT = "jcr:root";
/**
* NamespacerRegistry.
*/
private final NamespaceRegistry namespaceRegistry;
/**
* If noRecurse is true then only the node at absPath and its properties, but not its child nodes,
* are serialized. If noRecurse is false then the entire subtree rooted at absPath is serialized.
*/
private final boolean noRecurse;
/**
* Skip binary.
*/
private final boolean skipBinary;
/**
* SV namespace uri.
*/
private final String svNamespaceUri;
/**
* ValueFactory.
*/
private final ValueFactoryImpl systemValueFactory;
/**
* @param dataManager - ItemDataConsumer
* @param namespaceRegistry - NamespaceRegistry
* @param systemValueFactory - default ValueFactory
* @param skipBinary - If skipBinary is true then any properties of PropertyType.BINARY will be
* serialized as if they are empty.
* @param maxLevel - maximum level
* @param noRecurse - noRecurse value
* @exception RepositoryException if an repository error occurs.
*/
public BaseXmlExporter(ItemDataConsumer dataManager, NamespaceRegistry namespaceRegistry,
ValueFactoryImpl systemValueFactory, boolean skipBinary, boolean noRecurse, int maxLevel)
throws RepositoryException
{
super(dataManager, maxLevel);
this.skipBinary = skipBinary;
this.noRecurse = noRecurse;
this.namespaceRegistry = namespaceRegistry;
this.svNamespaceUri = namespaceRegistry.getURI("sv");
this.systemValueFactory = systemValueFactory;
}
/**
* @param node - exported node.
* @throws RepositoryException
* @throws SAXException
* @throws XMLStreamException
*/
public abstract void export(NodeData node) throws RepositoryException, SAXException, XMLStreamException;
/**
* @return - uri of the sv namespace.
*/
public String getSvNamespaceUri()
{
return svNamespaceUri;
}
/**
* @return - noRecurse.
*/
public boolean isNoRecurse()
{
return noRecurse;
}
/**
* @return - skip binary.
*/
public boolean isSkipBinary()
{
return skipBinary;
}
/**
* {@inheritDoc}
*/
public void visit(NodeData node) throws RepositoryException
{
try
{
entering(node, currentLevel);
if ((maxLevel == -1) || (currentLevel < maxLevel))
{
currentLevel++;
List<PropertyData> properies = new ArrayList<PropertyData>(dataManager.getChildPropertiesData(node));
// Sorting properties
Collections.sort(properies, new PropertyDataOrderComparator());
for (PropertyData data : properies)
{
InternalQName propName = data.getQPath().getName();
// 7.3.3 Respecting Property Semantics
// When an element or attribute representing such a property is
// encountered, an implementation may either skip it or respect it.
if (Constants.JCR_LOCKISDEEP.equals(propName) || Constants.JCR_LOCKOWNER.equals(propName))
{
continue;
}
data.accept(this);
}
if (!isNoRecurse() && (currentLevel > 0))
{
List<NodeData> nodes = new ArrayList<NodeData>(dataManager.getChildNodesData(node));
// Sorting nodes
Collections.sort(nodes, new NodeDataOrderComparator());
for (NodeData data : nodes)
{
data.accept(this);
}
}
currentLevel--;
}
leaving(node, currentLevel);
}
catch (RepositoryException re)
{
currentLevel = 0;
throw re;
}
}
/**
* @param data - exported ItemData.
* @param encode - is ISO9075 encode.
* @return - exported item name.
* @exception RepositoryException if an repository error occurs.
*/
protected String getExportName(ItemData data, boolean encode) throws RepositoryException
{
String nodeName;
QPath itemPath = data.getQPath();
if (Constants.ROOT_PATH.equals(itemPath))
{
nodeName = JCR_ROOT;
}
else
{
InternalQName internalNodeName = itemPath.getName();
if (encode)
{
internalNodeName = ISO9075.encode(itemPath.getName());
}
String prefix = namespaceRegistry.getPrefix(internalNodeName.getNamespace());
nodeName = prefix.length() == 0 ? "" : prefix + ":";
if ("".equals(itemPath.getName().getName()) && itemPath.isDescendantOf(Constants.EXO_NAMESPACES_PATH))
{
nodeName += DEFAULT_EMPTY_NAMESPACE_PREFIX; //NOSONAR
}
else
{
nodeName += internalNodeName.getName(); //NOSONAR
}
}
return nodeName;
}
/**
* @param data - exported value data.
* @param type - value type
* @return - string representation of values prepared for export. Be attentive method encode
* binary values in memory. It is possible OutOfMemoryError on large Values.
* @throws IllegalStateException
* @throws IOException
* @exception RepositoryException if an repository error occurs.
* @exception IOException if an I/O error occurs.
*/
protected String getValueAsStringForExport(ValueData data, int type) throws IOException, RepositoryException
{
String charValue = null;
switch (type)
{
case PropertyType.BINARY :
if (skipBinary)
{
charValue = "";
}
else
{
charValue = Base64.encode(data.getAsByteArray(), 0, (int)data.getLength(), 0, "");
}
break;
case PropertyType.NAME :
case PropertyType.DATE :
case PropertyType.PATH :
try
{
charValue = systemValueFactory.loadValue(data, type).getString();
}
catch (ValueFormatException e)
{
throw new RepositoryException(e);
}
catch (UnsupportedRepositoryOperationException e)
{
throw new RepositoryException(e);
}
break;
default :
charValue = ValueDataUtil.getString(data);
break;
}
return charValue;
}
/**
* Indicates whether or not the provided String contains only
* characters that are valid according to the specification of
* XML 1.0 that are defined here http://www.w3.org/TR/xml/#charsets
* @param content the content to check
* @return <code>true</code> if all the characters of the provided
* String are valid regarding the XML specification, <code>false</code>
* otherwise
*/
protected static boolean hasValidCharsOnly(String content)
{
if (content == null)
return true;
for (int i = 0, length = content.length(); i < length; i++)
{
char c = content.charAt(i);
if ((c == 0x9) || (c == 0xA) || (c == 0xD) || ((c >= 0x20) && (c <= 0xD7FF))
|| ((c >= 0xE000) && (c <= 0xFFFD)) || ((c >= 0x10000) && (c <= 0x10FFFF)))
{
// The character is valid
continue;
}
// The character is not valid
return false;
}
return true;
}
public NamespaceRegistry getNamespaceRegistry()
{
return namespaceRegistry;
}
public ValueFactoryImpl getSystemValueFactory()
{
return systemValueFactory;
}
}