/*
* 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.core.nodetype.registration;
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.PropertyDefinitionData;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.NamespaceRegistryImpl;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jcr.RepositoryException;
import javax.jcr.version.OnParentVersionAction;
/**
* Created by The eXo Platform SAS.<br>
* Class provides CND grammar manipulation tool for writing list of node types
* and concomitant namespaces to stream in CDN text format.
*
* @author <a href="mailto:nikolazius@gmail.com">Nikolay Zamosenchuk</a>
* @version $Id: $
*/
public class CNDStreamWriter
{
/**
* RegistryImpl instance
*/
private final NamespaceRegistryImpl namespaceRegistry;
/**
* Location factory instance
*/
private final LocationFactory locationFactory;
/**
* Constructs instance of CNDStreamWriter. Instance of NamespaceRegistryImpl
* is used to convert InternalQnames to strings.
*
* @param namespaceRegistry
*/
public CNDStreamWriter(NamespaceRegistryImpl namespaceRegistry)
{
this.namespaceRegistry = namespaceRegistry;
this.locationFactory = new LocationFactory(namespaceRegistry);
}
/**
* Write given list of node types to output stream.
*
* @param nodeTypes
* List of NodeTypes to write.
* @param os
* OutputStream to write to.
* @throws RepositoryException
*/
public void write(List<NodeTypeData> nodeTypes, OutputStream os) throws RepositoryException
{
OutputStreamWriter out = new OutputStreamWriter(os);
try
{
for (NodeTypeData nodeType : nodeTypes)
{
printNamespaces(nodeType, out);
printNodeTypeDeclaration(nodeType, out);
}
out.close();
}
catch (IOException e)
{
throw new RepositoryException(e.getMessage(), e);
}
}
/**
* Print namespaces to stream
*
* @param nodeTypeData
* @param out
* @throws RepositoryException
* @throws IOException
*/
private void printNamespaces(NodeTypeData nodeTypeData, OutputStreamWriter out) throws RepositoryException,
IOException
{
/**
* Using set to store all prefixes found in node types to avoid
* duplication
*/
Set<String> namespaces = new HashSet<String>();
/** Scanning nodeType definition for used namespaces */
printNameNamespace(nodeTypeData.getName(), namespaces);
printNameNamespace(nodeTypeData.getPrimaryItemName(), namespaces);
if (nodeTypeData.getDeclaredSupertypeNames() != null)
{
for (InternalQName reqType : nodeTypeData.getDeclaredSupertypeNames())
{
printNameNamespace(reqType, namespaces);
}
}
/** Scanning property definitions for used namespaces */
if (nodeTypeData.getDeclaredPropertyDefinitions() != null)
{
for (PropertyDefinitionData property : nodeTypeData.getDeclaredPropertyDefinitions())
{
printNameNamespace(property.getName(), namespaces);
}
}
/** Scanning child definitions for used namespaces */
if (nodeTypeData.getDeclaredChildNodeDefinitions() != null)
{
for (NodeDefinitionData child : nodeTypeData.getDeclaredChildNodeDefinitions())
{
printNameNamespace(child.getName(), namespaces);
printNameNamespace(child.getDefaultPrimaryType(), namespaces);
if (child.getRequiredPrimaryTypes() != null)
{
for (InternalQName reqType : child.getRequiredPrimaryTypes())
{
printNameNamespace(reqType, namespaces);
}
}
}
}
for (String prefix : namespaces)
{
String uri = namespaceRegistry.getURI(prefix);
out.write("<" + prefix + "='" + uri + "'>\r\n");
}
}
private void printNameNamespace(InternalQName name, Set<String> namespaces) throws RepositoryException
{
/** Adding current name's prefix to set if it is not default */
if (name != null)
{
String prefix = locationFactory.createJCRName(name).getPrefix();
if (!namespaceRegistry.isDefaultPrefix(prefix))
{
namespaces.add(prefix);
}
}
}
/**
* Method recursively print to output stream node type definition in cnd
* format.
*
* @param nodeTypeData
* is nodeType to print
* @throws RepositoryException
* this exception may be while converting QNames to string
* @throws IOException
* this exception my be thrown during some operations with streams
*/
private void printNodeTypeDeclaration(NodeTypeData nodeTypeData, OutputStreamWriter out) throws RepositoryException,
IOException
{
/** Print name */
out.write("[" + qNameToString(nodeTypeData.getName()) + "] ");
/** Print supertypes */
InternalQName[] superTypes = nodeTypeData.getDeclaredSupertypeNames();
if (superTypes != null && superTypes.length > 0)
{
/** if there is only 1 element and it is NT_BASE then avoid printing it */
if (superTypes.length > 1 || !superTypes[0].equals(Constants.NT_BASE))
{
out.write("> " + qNameToString(superTypes[0]));
for (int i = 1; i < superTypes.length; i++)
{
out.write(", " + qNameToString(superTypes[i]));
}
}
}
/** Print attributes */
StringBuilder attributes = new StringBuilder();
// if (nodeTypeData.isAbstract())
// {
// attributes += "abstract ";
// }
if (nodeTypeData.hasOrderableChildNodes())
{
attributes.append("orderable ");
}
if (nodeTypeData.isMixin())
{
attributes.append("mixin ");
}
// if (!nodeTypeData.isQueryable())
// {
// attributes += "noquery ";
// }
if (nodeTypeData.getPrimaryItemName() != null)
{
attributes.append("primaryitem " + qNameToString(nodeTypeData.getPrimaryItemName()));
}
if (attributes.length() > 0)
{
out.write("\r\n ");
out.write(attributes.toString());
}
/** Print all property definitions */
PropertyDefinitionData[] propertyDefinitions = nodeTypeData.getDeclaredPropertyDefinitions();
if (propertyDefinitions != null)
{
for (PropertyDefinitionData propertyDefinition : propertyDefinitions)
{
printPropertyDeclaration(propertyDefinition, out);
}
}
/** Print all child definitions */
NodeDefinitionData[] nodeDefinitions = nodeTypeData.getDeclaredChildNodeDefinitions();
if (nodeDefinitions != null)
{
for (NodeDefinitionData nodeDefinition : nodeDefinitions)
{
printChildDeclaration(nodeDefinition, out);
}
}
out.write("\r\n");
}
/**
* Prints to output stream property definition in CND format
*
* @param propertyDefinition
* @throws IOException
* @throws RepositoryException
*/
private void printPropertyDeclaration(PropertyDefinitionData propertyDefinition, OutputStreamWriter out)
throws IOException, RepositoryException
{
/** Print name */
out.write("\r\n ");
out.write("- " + qNameToString(propertyDefinition.getName()));
out.write(" (" + ExtendedPropertyType.nameFromValue(propertyDefinition.getRequiredType()).toUpperCase() + ")");
/** Print default values */
out.write(listToString(propertyDefinition.getDefaultValues(), "'", "\r\n = ", " "));
/** Print attributes */
StringBuilder attributes = new StringBuilder();
if (propertyDefinition.isAutoCreated())
{
attributes.append("autocreated ");
}
if (propertyDefinition.isMandatory())
{
attributes.append("mandatory ");
}
if (propertyDefinition.isProtected())
{
attributes.append("protected ");
}
if (propertyDefinition.isMultiple())
{
attributes.append("multiple ");
}
// if (!propertyDefinition.isFullTextSearchable())
// {
// attributes += "nofulltext ";
// }
// if (!propertyDefinition.isQueryOrderable())
// {
// attributes += "noqueryorder ";
// }
// /** Print operators avoiding printing all */
// String[] opArray = propertyDefinition.getAvailableQueryOperators();
// /** Using set to avoid duplication */
// Set<String> opSet = new HashSet<String>();
// for (String op : opArray)
// {
// opSet.add(op.toUpperCase());
// }
// /** if not all elements are mentioned in list then print */
// if (opSet.size() < 7 && opSet.size() > 0)
// {
// String opString = opSet.toString();
// // opString="queryops '"+opString.substring(1, opString.length()-1)+"' ";
// opString = listToString(opSet.toArray(new String[opSet.size()]), "", "\r\n queryops '", "' ");
// attributes += opString;
// }
if (propertyDefinition.getOnParentVersion() != OnParentVersionAction.COPY)
{
attributes.append("\r\n " + OnParentVersionAction.nameFromValue(propertyDefinition.getOnParentVersion()) + " ");
}
/** Don't print if all attributes are default */
if (attributes.length() > 0)
{
out.write("\r\n ");
out.write(attributes.toString());
}
out.write(listToString(propertyDefinition.getValueConstraints(), "'", "\r\n < ", " "));
}
/**
* Print to output stream child node definition in CND format
*
* @param nodeDefinition
* @throws IOException
* @throws RepositoryException
*/
private void printChildDeclaration(NodeDefinitionData nodeDefinition, OutputStreamWriter out) throws IOException,
RepositoryException
{
out.write("\r\n ");
out.write("+ " + qNameToString(nodeDefinition.getName()) + " ");
InternalQName[] requiredTypes = nodeDefinition.getRequiredPrimaryTypes();
if (requiredTypes != null && requiredTypes.length > 0)
{
/** if there is only 1 element and it is NT_BASE then avoid printing it */
if (requiredTypes.length > 1 || !requiredTypes[0].equals(Constants.NT_BASE))
{
out.write("(" + qNameToString(requiredTypes[0]));
for (int i = 1; i < requiredTypes.length; i++)
{
out.write(", " + qNameToString(requiredTypes[i]));
}
out.write(")");
}
}
if (nodeDefinition.getDefaultPrimaryType() != null)
{
out.write("\r\n = " + qNameToString(nodeDefinition.getDefaultPrimaryType()));
}
StringBuilder attributes = new StringBuilder();
if (nodeDefinition.isAutoCreated())
{
attributes.append("autocreated ");
}
if (nodeDefinition.isMandatory())
{
attributes.append("mandatory ");
}
if (nodeDefinition.isProtected())
{
attributes.append("protected ");
}
if (nodeDefinition.isAllowsSameNameSiblings())
{
attributes.append("sns ");
}
if (nodeDefinition.getOnParentVersion() != OnParentVersionAction.COPY)
{
attributes.append("\r\n " + OnParentVersionAction.nameFromValue(nodeDefinition.getOnParentVersion()) + " ");
}
if (attributes.length() > 0)
{
out.write("\r\n ");
out.write(attributes.toString());
}
}
/**
* Converts String[] to String using given notation:
* "beforeString+quote+element+quote+', '+quote+element...+quote+afterString"
*
* @param list
* Array to print
* @param quote
* Quote string for each element of array
* @param beforeString
* starting string
* @param afterString
* ending string
* @return
*/
private String listToString(String[] list, String quote, String beforeString, String afterString)
{
StringBuilder result = new StringBuilder();
if (list != null && list.length > 0)
{
result.append(beforeString);
result.append(quote + list[0] + quote);
for (int i = 1; i < list.length; i++)
{
result.append(", " + quote + list[i] + quote);
}
result.append(afterString);
}
return result.toString();
}
/**
* Converting InternalQName to String, using defined LocationFactory.
*
* @param name
* @return
* @throws RepositoryException
*/
private String qNameToString(InternalQName qName) throws RepositoryException
{
return locationFactory.createJCRName(qName).getAsString();
}
}