optionalProps)
+ {
+ Properties props = _exportProperties.get(key);
+ if (props == null)
+ {
+ final String msg = "Unable to proceed to the export. '" + key + "' properties are not set.";
+ _logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ // Check mandatory properties
+ for (String propKey : mandatoryProps)
+ {
+ Object o = props.get(propKey);
+ if (o != null)
+ {
+ if (!_internalTestPropertyInstance(propKey, o))
+ {
+ final String msg = "Unable to proceed to the export. Mandatory property '" + propKey + "' has not the expected type (properties '" + key + "').";
+ _logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+ else
+ {
+ final String msg = "Unable to proceed to the export. Mandatory property '" + propKey + "' is not set for properties '" + key + "'.";
+ _logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+
+ // Check optional properties
+ for (String propKey : optionalProps)
+ {
+ Object o = props.get(propKey);
+ if (o != null)
+ {
+ if (!_internalTestPropertyInstance(propKey, o))
+ {
+ final String msg = "Unable to proceed to the export. Optional property '" + propKey + "' has not the expected type (properties '" + key + "').";
+ _logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+ }
+
+ return props;
+ }
+
+ private boolean _internalTestPropertyInstance(String propKey, Object o)
+ {
+ return _propertyTypes.containsKey(propKey) && _propertyTypes.get(propKey).isInstance(o);
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/JcrImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/JcrImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/JcrImporter.java (revision 0)
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.commons.io.IOUtils;
+import org.apache.excalibur.xml.sax.ContentHandlerProxy;
+import org.apache.excalibur.xml.sax.SAXParser;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * JcrImporter
+ */
+public class JcrImporter extends AbstractLogEnabled implements Component, Serviceable
+{
+ /** SAX Parser */
+ protected SAXParser _saxParser;
+
+ @Override
+ public void service(ServiceManager manager) throws ServiceException
+ {
+ _saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
+ }
+
+ /**
+ * JCR import of a Node into, using JCR System View. The incoming
+ * inputStream will not be closed during the execution of this method.
+ *
+ * The passed inputStream is closed before this method returns either
+ * normally or because of an exception.
+ *
+ * @param parentNode Node in which the import will be done.
+ * @param uuidBehavior the {@link ImportUUIDBehavior} to be used during the
+ * import.
+ * @param inputStream Input JCR System view XML to be imported.
+ * @param importHandlerProxy An optional import handler proxy that will act
+ * as a bridge between the inputStream and the JCR import
+ * handler.
+ * @throws RepositoryException
+ * @throws IOException
+ * @throws SAXException
+ */
+ public void importNode(Node parentNode, int uuidBehavior, InputStream inputStream, ContentHandlerProxy importHandlerProxy) throws RepositoryException, SAXException, IOException
+ {
+ try
+ {
+ final Session session = parentNode.getSession();
+ final String parentAbsPath = parentNode.getPath();
+
+ // JCR import handler
+ ContentHandler importHandler = session.getImportContentHandler(parentAbsPath, uuidBehavior);
+
+ // Optional proxy handler
+ if (importHandlerProxy != null)
+ {
+ importHandlerProxy.setContentHandler(importHandler);
+ importHandler = importHandlerProxy;
+ }
+
+ // Parse the input source and send the events to the import handler
+ InputSource inputSource = new InputSource(inputStream);
+ _saxParser.parse(inputSource, importHandler);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+
+ /**
+ * Convenient method to import an inputStream as a JCR binary property of a
+ * Node.
+ *
+ * The passed inputStream is closed before this method returns either
+ * normally or because of an exception.
+ *
+ * @param node Node for which the property will be set.
+ * @param propertyName The name of the property to set.
+ * @param inputStream The inputStream consuming the binary data to import.
+ * @throws RepositoryException
+ */
+ public void importBinaryProperty(Node node, String propertyName, InputStream inputStream) throws RepositoryException
+ {
+ Binary binary = null;
+ try
+ {
+ final ValueFactory vf = node.getSession().getValueFactory();
+ binary = vf.createBinary(inputStream);
+ node.setProperty(propertyName, binary);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(inputStream);
+
+ if (binary != null)
+ {
+ binary.dispose();
+ }
+ }
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/BinaryPropertiesImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/BinaryPropertiesImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/BinaryPropertiesImporter.java (revision 0)
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.io.InputStream;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Binary properties importer using the JCR Import.
+ */
+public class BinaryPropertiesImporter extends JcrImporter
+{
+ /** Avalon role. */
+ public static final String ROLE = BinaryPropertiesImporter.class.getName();
+
+ /** input data list property */
+ public static final String IMPORT_DATA_LIST_PROPERTY = "importDataList";
+
+ /**
+ * Import binary properties
+ * @param importSession
+ * @param importDataList
+ * @throws RepositoryException
+ */
+ public void doImport(Session importSession, List importDataList) throws RepositoryException
+ {
+ for (ImportData data : importDataList)
+ {
+ Node node = importSession.getNodeByIdentifier(data._nodeIdentifier);
+ importBinaryProperty(node, data._propertyName, data._binaryStream);
+
+ importSession.save();
+ }
+ }
+
+ /**
+ * Import data structure used to execute the import. An instance of this
+ * class corresponds to an binary property to import.
+ */
+ public static class ImportData
+ {
+ /** The JCR identifier of the node on which the property will be set */
+ protected String _nodeIdentifier;
+ /** The name of the property to import */
+ protected String _propertyName;
+ /** Inputstream consuming the binary data to import */
+ protected InputStream _binaryStream;
+
+ /**
+ * Ctor
+ * @param nodeIdentifier
+ * @param propertyName
+ * @param binaryStream
+ */
+ public ImportData(String nodeIdentifier, String propertyName, InputStream binaryStream)
+ {
+ _nodeIdentifier = nodeIdentifier;
+ _propertyName = propertyName;
+ _binaryStream = binaryStream;
+ }
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/WorkflowImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/WorkflowImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/WorkflowImporter.java (revision 0)
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.cms.io.handlers.ImportWorkflowHandler;
+
+/**
+ * Workflow importer using the JCR Import.
+ */
+public class WorkflowImporter extends JcrImporter
+{
+ /** Avalon role. */
+ public static final String ROLE = WorkflowImporter.class.getName();
+
+ /** Workflow input stream property */
+ public static final String INPUT_STREAM_PROPERTY = "inputStream";
+
+ /**
+ * Import workflow nodes from an inputstream.
+ * @param importSession The session in which the import will be done.
+ * @param defaultSession
+ * @param inputStream
+ * @param referenceTracker
+ * @return the importWorkflowHandler
+ * @throws RepositoryException
+ * @throws SAXException
+ * @throws IOException
+ */
+ public ImportWorkflowHandler doImport(Session importSession, Session defaultSession, InputStream inputStream, ImportReferenceTracker referenceTracker) throws RepositoryException, SAXException, IOException
+ {
+ Node rootWfNodeDefaultWS = defaultSession.getRootNode().getNode(IOConstants.OSWF_ROOT);
+ Node rootWorkflowNode = _getRootWorkflowNode(importSession, defaultSession, rootWfNodeDefaultWS);
+
+ // Get the import handler
+ int uuidBehavior = ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW; // We can create new uuid for workflow entry, to reduce collision risk.
+ long nextEntryId = rootWfNodeDefaultWS.getProperty(IOConstants.NEXT_ENTRY_ID_PROPERTY).getLong();
+ ImportWorkflowHandler importWorkflowHandler = new ImportWorkflowHandler(rootWorkflowNode, uuidBehavior, referenceTracker, nextEntryId);
+
+ // Performing the import.
+ importNode(rootWorkflowNode, uuidBehavior, inputStream, importWorkflowHandler);
+
+ // Update root workflow node (default workspace) next entry id property
+ rootWfNodeDefaultWS.setProperty(IOConstants.NEXT_ENTRY_ID_PROPERTY, importWorkflowHandler.getCurrentEntryId() + 1);
+
+ return importWorkflowHandler;
+ }
+
+ private Node _getRootWorkflowNode(Session importSession, Session defaultSession, Node rootWfNodeDefaultWS) throws RepositoryException
+ {
+ if (!importSession.getWorkspace().getName().equals(defaultSession.getWorkspace().getName()))
+ {
+ return JcrUtils.getOrAddNode(importSession.getRootNode(), IOConstants.OSWF_ROOT, IOConstants.OSWF_ROOT_NT);
+ }
+
+ return rootWfNodeDefaultWS;
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporterModifier.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporterModifier.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporterModifier.java (revision 0)
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers.jcr.versions;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+
+/**
+ * VersionImporterModifier
allows to modify version properties by
+ * extending ProtectedItemModifier
+ */
+public class VersionImporterModifier extends ProtectedItemModifier
+{
+ /**
+ * VersionImporterModifier ctor
+ */
+ public VersionImporterModifier()
+ {
+ super(Permission.VERSION_MNGMT);
+ }
+
+ /**
+ * Set the internal value of a the {@link NameConstants#JCR_VERSIONHISTORY}
+ * property without checking any constraints.
+ * @param parentImpl
+ * @param value
+ * @return the modified property
+ * @throws RepositoryException
+ */
+ public Property setVersionHistoryProperty(NodeImpl parentImpl, Value value) throws RepositoryException
+ {
+ return setProperty(parentImpl, NameConstants.JCR_VERSIONHISTORY, value);
+ }
+
+ /**
+ * Set the internal value of a the {@link NameConstants#JCR_BASEVERSION}
+ * property without checking any constraints.
+ * @param parentImpl
+ * @param value
+ * @return the modified property
+ * @throws RepositoryException
+ */
+ public Property setBaseVersionProperty(NodeImpl parentImpl, Value value) throws RepositoryException
+ {
+ return setProperty(parentImpl, NameConstants.JCR_BASEVERSION, value);
+ }
+
+ /**
+ * Set the internal value of a the {@link NameConstants#JCR_VERSIONHISTORY}
+ * property without checking any constraints.
+ * @param parentImpl
+ * @param values
+ * @return the modified property
+ * @throws RepositoryException
+ */
+ public Property setPredecessorsProperty(NodeImpl parentImpl, Value[] values) throws RepositoryException
+ {
+ return setProperty(parentImpl, NameConstants.JCR_PREDECESSORS, values);
+ }
+
+ /**
+ * Set the internal value of a the {@link NameConstants#JCR_ISCHECKEDOUT}
+ * property without checking any constraints.
+ * @param parentImpl
+ * @param value
+ * @return the modified property
+ * @throws RepositoryException
+ */
+ public Property setIsCheckedOutProperty(NodeImpl parentImpl, Value value) throws RepositoryException
+ {
+ return setProperty(parentImpl, NameConstants.JCR_ISCHECKEDOUT, value);
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/jcr/versions/VersionImporter.java (revision 0)
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers.jcr.versions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
+import org.apache.jackrabbit.core.xml.DefaultProtectedPropertyImporter;
+import org.apache.jackrabbit.core.xml.PropInfo;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+
+/**
+ * VersionImporter
implements a DefaultProtectedPropertyImporter
+ * that set the following internal version properties which are protected by default :
+ *
+ * - {@link NameConstants#JCR_VERSIONHISTORY}
+ * - {@link NameConstants#JCR_BASEVERSION}
+ * - {@link NameConstants#JCR_PREDECESSORS}
+ * - {@link NameConstants#JCR_ISCHECKEDOUT}
+ *
+ */
+public class VersionImporter extends DefaultProtectedPropertyImporter
+{
+ private final List _propertyNames = Arrays.asList(NameConstants.JCR_VERSIONHISTORY, NameConstants.JCR_BASEVERSION, NameConstants.JCR_PREDECESSORS, NameConstants.JCR_ISCHECKEDOUT);
+ private VersionImporterModifier _versionImporterModifier;
+
+ @Override
+ public boolean init(JackrabbitSession aSession, NamePathResolver aResolver, boolean aIsWorkspaceImport, int aUuidBehavior, ReferenceChangeTracker aReferenceTracker)
+ {
+ _versionImporterModifier = new VersionImporterModifier();
+ return super.init(aSession, aResolver, aIsWorkspaceImport, aUuidBehavior, aReferenceTracker);
+ }
+
+ // -----------------------------------------< ProtectedPropertyImporter >---
+ @Override
+ public boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException
+ {
+ if (parent.isNodeType(NameConstants.MIX_VERSIONABLE))
+ {
+ Name propName = protectedPropInfo.getName();
+ if (_propertyNames.contains(propName))
+ {
+ // convert serialized values to Value objects
+ Value[] va = protectedPropInfo.getValues(protectedPropInfo.getTargetType(def), resolver);
+
+ // set version properties
+ if (propName.equals(NameConstants.JCR_VERSIONHISTORY))
+ {
+ if (referencedNodeExists(va[0]))
+ {
+ _versionImporterModifier.setVersionHistoryProperty(parent, va[0]);
+ return true;
+ }
+ }
+ else if (propName.equals(NameConstants.JCR_BASEVERSION))
+ {
+ if (referencedNodeExists(va[0]))
+ {
+ _versionImporterModifier.setBaseVersionProperty(parent, va[0]);
+ return true;
+ }
+ }
+ else if (propName.equals(NameConstants.JCR_PREDECESSORS))
+ {
+ Value[] existings = referencedNodesExist(va);
+ if (existings != null)
+ {
+ _versionImporterModifier.setPredecessorsProperty(parent, existings);
+ return true;
+ }
+ }
+ else if (propName.equals(NameConstants.JCR_ISCHECKEDOUT))
+ {
+ _versionImporterModifier.setIsCheckedOutProperty(parent, va[0]);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ //------------------------------------------------------------< private >---
+ /**
+ * Test is the node referenced by the value exists.
+ *
+ * @param value
+ * @throws RepositoryException
+ */
+ private boolean referencedNodeExists(Value value) throws RepositoryException
+ {
+ try
+ {
+ session.getNodeByIdentifier(value.getString());
+ return true;
+ }
+ catch (ItemNotFoundException e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Filter values referencing an existing node only.
+ *
+ * @param values values to be filtered
+ * @return An array of the filtered values.
+ * @throws RepositoryException
+ */
+ private Value[] referencedNodesExist(Value[] values) throws RepositoryException
+ {
+ List existings = new ArrayList();
+ for (int i = 0; i < values.length; i++)
+ {
+ if (referencedNodeExists(values[0]))
+ {
+ existings.add(values[0]);
+ }
+ }
+
+ return existings.isEmpty() ? null : existings.toArray(new Value[existings.size()]);
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/ImportReferenceTracker.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/ImportReferenceTracker.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/ImportReferenceTracker.java (revision 0)
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+
+/**
+ * Helper class that tracks remapped uuid's and imported reference
+ * that might need adjusting at the end of the import.
+ */
+public class ImportReferenceTracker
+{
+ /**
+ * Map that tracks nodes of interest for which a newly identifier (uuid) may have been
+ * assigned during the import.
+ * Tracking should be done for node extending one of these jcr:primaryType :
+ *
+ * - ametys:defaultContent (can have a workflowRef)
+ * - oswf:entry (have a contentRef)
+ *
+ */
+ protected Map _newUUIDTracker = new HashMap();
+
+ /** Map that tracks the imported 'ametys-internal:workflowRef' properties */
+ private final List _workflowRefsTracker = new ArrayList();
+
+ /** Map that tracks the imported 'ametys-internal:contentRef' properties */
+ private final List _contentRefsTracker = new ArrayList();
+
+ /**
+ * Map that tracks the other imported properties of type
+ * {@link PropertyType#REFERENCE} or {@link PropertyType#WEAKREFERENCE} or {@link PropertyType#PATH}
+ * for later processing.
+ */
+ private final List _otherRefsTracker = new ArrayList();
+
+ /**
+ * Store the given uuid mapping for later processing.
+ * @param oldUUID old node uuid
+ * @param newUUID new node uuid
+ */
+ public void putUUIDChange(String oldUUID, String newUUID)
+ {
+ _newUUIDTracker.put(oldUUID, newUUID);
+ }
+
+ /**
+ * Add a new property to be tracked.
+ * Should be an 'ametys-internal:workflowRef'.
+ * @param property
+ */
+ public void tracksWorkflowRef(Property property)
+ {
+ _workflowRefsTracker.add(property);
+ }
+
+ /**
+ * Add a new property to be tracked.
+ * Should be an 'ametys-internal:contentRef'
+ * @param property The JCR property object
+ */
+ public void tracksContentRef(Property property)
+ {
+ _contentRefsTracker.add(property);
+ }
+
+ /**
+ * Add a new property to be tracked.
+ * @param property The JCR property object
+ */
+ public void tracksOthersRef(Property property)
+ {
+ _otherRefsTracker.add(property);
+ }
+
+ /**
+ * Returns _newUUIDTracker
+ * @return _newUUIDTracker
+ */
+ public Map getUUIDChanges()
+ {
+ return _newUUIDTracker;
+ }
+
+ /**
+ * Returns the list of the tracked properties that a related to the workflows.
+ * @return the tracked properties (contentRef or workflowRef)
+ */
+ public List getWfTrackedRefs()
+ {
+ List refs = new ArrayList(_workflowRefsTracker);
+ refs.addAll(_contentRefsTracker);
+ return refs;
+ }
+
+ /**
+ * Returns the others tracked references.
+ * @return list of {@link Property}
+ */
+ public List getOtherRefs()
+ {
+ return _otherRefsTracker;
+ }
+
+ /** Resets all internal state. */
+ public void clear()
+ {
+ _newUUIDTracker.clear();
+ _workflowRefsTracker.clear();
+ _contentRefsTracker.clear();
+ _otherRefsTracker.clear();
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/ResourcesImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/ResourcesImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/ResourcesImporter.java (revision 0)
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.xml.sax.SAXException;
+
+import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory;
+
+/**
+ * Ametys resources importer using the JCR Import.
+ */
+public class ResourcesImporter extends JcrImporter
+{
+ /** Avalon role. */
+ public static final String ROLE = ResourcesImporter.class.getName();
+
+ /** input resource node data list property */
+ public static final String IMPORT_RESOURCE_NODE_LIST_PROPERTY = "nodeDataList";
+
+ /** input binary resource data list property */
+ public static final String IMPORT_BINARY_RESOURCE_LIST_PROPERTY = "binaryDataList";
+
+ /**
+ * Import ametys resources
+ * @param importedNode
+ * @param nodeDataList
+ * @param binaryDataList
+ * @throws RepositoryException
+ * @throws IOException
+ * @throws SAXException
+ */
+ public void doImport(Node importedNode, List nodeDataList, List binaryDataList) throws RepositoryException, SAXException, IOException
+ {
+ final Session session = importedNode.getSession();
+
+ // An exception will be thrown if there is an identifier collision.
+ final int uuidBehavior = ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW;
+
+ // Import resource collection nodes.
+ for (ResourceNodeImportData nodeData : nodeDataList)
+ {
+ String relPath = nodeData._parentRelPath;
+ Node parentNode = StringUtils.isEmpty(relPath) ? importedNode : importedNode.getNode(nodeData._parentRelPath);
+
+ importNode(parentNode, uuidBehavior, nodeData._inputStream, null);
+ }
+
+ session.save();
+
+ // Import binary resources.
+ final String endResourcePath = '/' + JcrConstants.JCR_CONTENT;
+ final String dataPropertyName = JcrConstants.JCR_DATA;
+ for (BinaryResourceImportData binaryData : binaryDataList)
+ {
+ Node resourceNode = importedNode.getNode(binaryData._resourceRelPath + endResourcePath);
+ importBinaryProperty(resourceNode, dataPropertyName, binaryData._binaryStream);
+
+ session.save();
+ }
+ }
+
+ /**
+ * Import data structure used to execute the import. An instance of this
+ * class corresponds to an ametys:resources-collection
to
+ * import.
+ * @see JCRResourcesCollectionFactory#RESOURCESCOLLECTION_NODETYPE
+ */
+ public static class ResourceNodeImportData
+ {
+ /**
+ * The JCR path to the node into which the import will be done. The path
+ * is relative to the root imported node (site node, odf node...)
+ */
+ protected String _parentRelPath;
+ /** Inputstream consuming XML corresponding to the node to be imported */
+ protected InputStream _inputStream;
+
+ /**
+ * Ctor
+ * @param parentPath
+ * @param inputStream
+ */
+ public ResourceNodeImportData(String parentPath, InputStream inputStream)
+ {
+ _parentRelPath = parentPath;
+ _inputStream = inputStream;
+ }
+ }
+
+ /**
+ * Import data structure for binary resources.
+ */
+ public static class BinaryResourceImportData
+ {
+ /**
+ * The JCR path of the resource node that represents the binary data to
+ * be imported. The path is relative to the root imported node (site
+ * node, odf node...)
+ */
+ protected String _resourceRelPath;
+ /** Inputstream consuming the binary data to import */
+ protected InputStream _binaryStream;
+
+ /**
+ * Ctor
+ * @param resourcePath
+ * @param binaryStream
+ */
+ public BinaryResourceImportData(String resourcePath, InputStream binaryStream)
+ {
+ _resourceRelPath = resourcePath;
+ _binaryStream = binaryStream;
+ }
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/importers/AmetysImporter.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/importers/AmetysImporter.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/importers/AmetysImporter.java (revision 0)
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.importers;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.spi.Path;
+
+import org.ametys.cms.io.IOUtils;
+
+/**
+ * Abstract Ametys Importer.
+ * Ametys Importers (like SiteImporter, ODFImporter etc...) should extend this class.
+ */
+public abstract class AmetysImporter extends AbstractLogEnabled
+{
+ /**
+ * Adjust references collected during an import. This method try to ensure
+ * referential integrity. A diagnosis is also made for references that do
+ * not enforce referential integrity (weak reference, path..) when the value
+ * do not point to an existing node.
+ *
+ * @param importedNode
+ * @param referenceTracker
+ * @throws RepositoryException
+ */
+ protected void _adjustReferenceProperties(Node importedNode, ImportReferenceTracker referenceTracker) throws RepositoryException
+ {
+ final Session session = importedNode.getSession();
+
+ // Adjust contentRef / workflowRef references.
+ Map uuidChanges = referenceTracker.getUUIDChanges();
+ for (Property reference : referenceTracker.getWfTrackedRefs())
+ {
+ String original = reference.getString();
+ String adjusted = uuidChanges.get(original);
+ if (adjusted != null)
+ {
+ try
+ {
+ Node nodeToBeReferenced = session.getNodeByIdentifier(adjusted);
+ reference.setValue(nodeToBeReferenced);
+ }
+ catch (ItemNotFoundException infe)
+ {
+ String msg = "Expecting to find a referenced node with identifier '" + adjusted + "' but did not."
+ + "\nIts original property value was '" + original + "'";
+ getLogger().error(msg);
+ throw infe;
+ }
+ }
+ }
+
+ // Process external references.
+ for (Property property : referenceTracker.getOtherRefs())
+ {
+ _analyzeProperty(importedNode, property);
+ }
+
+ referenceTracker.clear();
+ }
+
+ private void _analyzeProperty(Node node, Property property) throws RepositoryException
+ {
+ if (property.isMultiple())
+ {
+ for (Value value : property.getValues())
+ {
+ _analyzeProperty(node, property, value);
+ }
+ }
+ else
+ {
+ _analyzeProperty(node, property, property.getValue());
+ }
+ }
+
+ private void _analyzeProperty(Node node, Property property, Value value) throws RepositoryException
+ {
+ int propType = property.getType();
+ switch (propType)
+ {
+ case PropertyType.REFERENCE:
+ case PropertyType.WEAKREFERENCE:
+ _analyzeReferenceProperty(node, property, value);
+ break;
+ case PropertyType.PATH:
+ _analyzePathProperty(node, property, value);
+ break;
+ default:
+ // should not happen.
+ getLogger().warn("unexpected property type");
+ }
+ }
+
+ private void _analyzeReferenceProperty(Node node, Property property, Value value) throws RepositoryException
+ {
+ try
+ {
+ node.getSession().getNodeByIdentifier(value.getString());
+ }
+ catch (ItemNotFoundException e)
+ {
+ String type = property.getType() == PropertyType.REFERENCE ? PropertyType.TYPENAME_REFERENCE : PropertyType.TYPENAME_WEAKREFERENCE;
+ String msg = "Node '" + property.getParent().getName() + "' has a property of type '" + type + "' with value '" + value.getString()
+ + "' and the referenced node no longer exists.";
+ getLogger().warn(msg, e);
+
+ if (PropertyType.TYPENAME_REFERENCE.equals(type))
+ {
+ // Currently removes the reference and log.
+ String removeMsg = "Property '" + property.getName() + "' referencing an external item has been removed to preserve referential integrity."
+ + "\nProperty path was : '" + property.getPath() + "'";
+ getLogger().warn(removeMsg);
+ property.remove();
+
+ }
+ }
+ }
+
+ private void _analyzePathProperty(Node node, Property property, Value value) throws RepositoryException
+ {
+ Session session = node.getSession();
+ if (!(session instanceof SessionImpl))
+ {
+ String msg = "Cannot test if a property of type '" + PropertyType.TYPENAME_PATH + " of the node '" + property.getParent().getName()
+ + "' reference an item which is outside the scope of the imported node.";
+ getLogger().warn(msg);
+ return;
+ }
+
+ String path = value.getString();
+ Path p = ((SessionImpl) session).getQPath(path);
+
+ boolean absolute = p.isAbsolute();
+ // Path property could either reference a Node or a Property
+ try
+ {
+ if (absolute)
+ {
+ session.getNode(path);
+ }
+ else
+ {
+ property.getParent().getNode(path);
+ }
+ }
+ catch (PathNotFoundException e)
+ {
+ try
+ {
+ if (absolute)
+ {
+ session.getProperty(path);
+ }
+ else
+ {
+ property.getParent().getProperty(path);
+ }
+ }
+ catch (PathNotFoundException e2)
+ {
+ // Currenlty, only logs a warning.
+ String msg = "Node '" + property.getParent().getName() + "' has a property of type '" + PropertyType.TYPENAME_PATH + "' with value '" + value.getString()
+ + "' and the referenced item no longer exists.";
+ getLogger().warn(msg, e);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Adjust versionable nodes after an import.
+ * @param importedNode
+ * @param exportDate
+ * @throws RepositoryException
+ */
+ protected void _adjustVersionableNodes(Node importedNode, Date exportDate) throws RepositoryException
+ {
+ Workspace importWorkspace = importedNode.getSession().getWorkspace();
+ VersionManager versionManager = importWorkspace.getVersionManager();
+
+ // Currently, versionable nodes in Ametys are ametys:content and ametys:resource.
+ NodeIterator nodeIterator = IOUtils.retrieveVersionableNodes(importedNode);
+
+ while (nodeIterator.hasNext())
+ {
+ Node node = nodeIterator.nextNode();
+ String path = node.getPath();
+ VersionHistory versionHistory = versionManager.getVersionHistory(path);
+
+ _removeNewerVersionsSinceExport(versionHistory, exportDate);
+
+ // Create a new version if only the root version exists.
+ // It happens when the export/import is done in a different repository.
+ _createNewVersionIfNecessary(versionManager, path);
+
+ // Additional processing
+ _additionalAdjustementForVersionableNode(node, versionHistory);
+ }
+ }
+
+ /**
+ * Additional processing on versionable node.
+ * @param node the node to process
+ * @param versionHistory the version history bound to this node
+ * @throws RepositoryException
+ */
+ protected void _additionalAdjustementForVersionableNode(Node node, VersionHistory versionHistory) throws RepositoryException
+ {
+ // Nothing to do
+ }
+
+ private void _removeNewerVersionsSinceExport(VersionHistory versionHistory, Date exportDate) throws RepositoryException
+ {
+ if (exportDate == null)
+ {
+ return;
+ }
+
+ boolean newerThanExport = true;
+ Iterator versionsIterator = IOUtils.getVersionSortedByCreatedDesc(versionHistory);
+ final String rootVersionName = versionHistory.getRootVersion().getName();
+ while (versionsIterator.hasNext() && newerThanExport)
+ {
+ Version version = versionsIterator.next();
+
+ if (version.getCreated().getTime().before(exportDate))
+ {
+ newerThanExport = false;
+ }
+ // Remove newer version, expect root version.
+ else if (!rootVersionName.equals(version.getName()))
+ {
+ versionHistory.removeVersion(version.getName());
+ }
+ }
+ }
+
+ private void _createNewVersionIfNecessary(VersionManager versionManager, String nodeAbsPath) throws RepositoryException
+ {
+ // Do a checkpoint if there is no initial version for this node. (rootVersion excluded)
+ VersionHistory versionHistory = versionManager.getVersionHistory(nodeAbsPath);
+ VersionIterator versions = versionHistory.getAllVersions();
+
+ versions.nextVersion(); // root version.
+ if (!versions.hasNext())
+ {
+ versionManager.checkpoint(nodeAbsPath);
+ }
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/handlers/ImportAmetysObjectHandler.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/handlers/ImportAmetysObjectHandler.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/handlers/ImportAmetysObjectHandler.java (revision 0)
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.handlers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.cms.io.IOHandler;
+import org.ametys.cms.io.exporters.JcrExporter;
+import org.ametys.cms.io.importers.ImportReferenceTracker;
+
+/**
+ * "Proxy" handler used to import an ametys object (site, odf..).
+ * Collects useful information that will be processed later.
+ */
+public class ImportAmetysObjectHandler extends IOHandler
+{
+ /** Ametys internal workflowRef property */
+ protected static final int IN_WORKFLOW_ID_PROPERTY = 12;
+
+ /** Ametys internal export date property */
+ protected static final int IN_EXPORT_DATE_PROPERTY = 13;
+
+ /** uuid property name */
+ protected static final String _UUID_PROPERTY_NAME = "uuid";
+
+ /** new workflow id property */
+ protected static final String _NEW_WORKFLOW_ID = "newWorkflowId";
+
+ /** export date property */
+ protected static final String _EXPORT_DATE_STRING = "exportDateStr";
+
+ /** references property */
+ protected static final String _REFERENCES_PROPERTY = "references";
+
+ /** The reference tracker to populate during import */
+ protected ImportReferenceTracker _referenceTracker;
+
+ /** Mapping of old oswf:entry id to new oswf:entry id */
+ protected Map _idMapping = new HashMap();
+
+ /** Export date retrieved during the import */
+ protected Date _exportDate;
+
+ /** Name of the imported node */
+ protected String _importedNodeName;
+
+ /** List of property types that denote a reference */
+ protected final List _referenceTypes = Arrays.asList(
+ PropertyType.TYPENAME_REFERENCE,
+ PropertyType.TYPENAME_WEAKREFERENCE,
+ PropertyType.TYPENAME_PATH);
+
+ /**
+ * Ctor inheritance
+ * @param parentNode the session in which the import is done
+ * @param referenceTracker
+ * @param oswfEntryIdMapping
+ */
+ public ImportAmetysObjectHandler(Node parentNode, ImportReferenceTracker referenceTracker, Map oswfEntryIdMapping)
+ {
+ super(new DefaultHandler(), parentNode);
+ _referenceTracker = referenceTracker;
+ _idMapping = oswfEntryIdMapping;
+ }
+
+ @Override
+ protected void _onNodeStart(Attributes atts)
+ {
+ // _importedNodeName is null when SAX'ing the root node which is the imported node.
+ if (_importedNodeName == null)
+ {
+ _importedNodeName = atts.getValue(IOConstants.SV_NAME);
+ }
+ }
+
+ @Override
+ protected void _onPropertyStart(Attributes atts)
+ {
+ String name = atts.getValue(IOConstants.SV_NAME);
+ String type = atts.getValue(IOConstants.SV_TYPE);
+
+ // Collecting the jcr:uuid of the current node
+ if (JcrConstants.JCR_UUID.equals(name))
+ {
+ _state = IN_JCR_UUID_PROPERTY;
+ }
+ else if (IOConstants.WORKFLOW_ID_PROPERTY.equals(name))
+ {
+ _state = IN_WORKFLOW_ID_PROPERTY;
+ }
+ else if (JcrExporter.EXPORT_DATE_PROPERTY.equals(name))
+ {
+ _state = IN_EXPORT_DATE_PROPERTY;
+ }
+ else if (_referenceTypes.contains(type) && !name.startsWith(Name.NS_JCR_PREFIX + ':'))
+ {
+ Properties props = _nodeStack.peek();
+
+ List references = (List) props.get(_REFERENCES_PROPERTY);
+ if (references == null)
+ {
+ references = new ArrayList();
+ props.put(_REFERENCES_PROPERTY, references);
+ }
+ references.add(name);
+ }
+ }
+
+ @Override
+ protected void _onValueStart(Attributes atts)
+ {
+ switch (_state)
+ {
+ case IN_JCR_UUID_PROPERTY:
+ case IN_WORKFLOW_ID_PROPERTY:
+ // Set the scan characters flag
+ _flags |= SCAN_CHARACTERS;
+ break;
+ case IN_EXPORT_DATE_PROPERTY:
+ // Set the scan characters flag
+ _flags |= SCAN_CHARACTERS;
+
+ // Stop forwarding events
+ _flags &= ~(FORWARD_STARTS | FORWARD_ENDS | FORWARD_CHARACTERS);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onNodeEnd() throws SAXException
+ {
+ Properties props = _nodeStack.peek();
+
+ // 1 - Retrieves imported node from path
+ String relPath = (String) props.get(PATH_PROPERTY_NAME);
+ if (relPath == null)
+ {
+ return;
+ }
+
+ try
+ {
+ Node node = _parentNode.getNode(relPath);
+
+ // 2 - Populates reference tracker
+ List references = (List) props.get(_REFERENCES_PROPERTY);
+ if (references != null && !references.isEmpty())
+ {
+ for (String refProperty : references)
+ {
+ Property property = node.getProperty(refProperty);
+ _referenceTracker.tracksOthersRef(property);
+ }
+ }
+
+ // 3 - Analyze primary type
+ if (!node.isNodeType(IOConstants.DEFAULT_CONTENT_NODETYPE))
+ {
+ return;
+ }
+
+ // 4 - Checks if its uuid has changed
+ String oldUUID = (String) props.get(_UUID_PROPERTY_NAME);
+ if (oldUUID == null)
+ {
+ String msg = "Expecting an UUID property for this node.";
+ _logger.error(msg);
+ throw new SAXException(msg);
+ }
+
+ String newUUID = node.getIdentifier();
+ if (!newUUID.equals(oldUUID))
+ {
+ _referenceTracker.putUUIDChange(oldUUID, newUUID);
+ }
+
+ // 5 - Update the workflow id property
+ Long newWorkflowId = (Long) props.get(_NEW_WORKFLOW_ID);
+ if (newWorkflowId == null)
+ {
+ String msg = "Expecting a mapped entry for the '" + IOConstants.WORKFLOW_ID_PROPERTY + "' property.";
+ _logger.warn(msg);
+ }
+ else
+ {
+ node.setProperty(IOConstants.WORKFLOW_ID_PROPERTY, newWorkflowId);
+ }
+
+ // 6 - Add reference to track
+ try
+ {
+ Property workflowRef = node.getProperty(IOConstants.WORKFLOW_REF_PROPERTY_NAME);
+ _referenceTracker.tracksWorkflowRef(workflowRef);
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Expecting a '" + IOConstants.WORKFLOW_REF_PROPERTY_NAME + "' property for this node.";
+ _logger.warn(msg, e);
+ }
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Error during the post processing step of the import of a node.";
+ _logger.error(msg, e);
+ throw new SAXException(msg, e);
+ }
+ }
+
+ @Override
+ protected void _onPropertyEnd()
+ {
+ switch (_state)
+ {
+ case IN_JCR_UUID_PROPERTY:
+ case IN_WORKFLOW_ID_PROPERTY:
+ _state = IN_NODE;
+ break;
+ case IN_EXPORT_DATE_PROPERTY:
+ _state = IN_NODE;
+ // Start forwarding events
+ _flags |= FORWARD_STARTS | FORWARD_ENDS | FORWARD_CHARACTERS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onValueEnd(String value) throws SAXException
+ {
+ switch (_state)
+ {
+ case IN_JCR_UUID_PROPERTY:
+ _nodeStack.peek().put(_UUID_PROPERTY_NAME, value);
+ break;
+ case IN_WORKFLOW_ID_PROPERTY:
+ Long oldWorkflowId = Long.valueOf(value);
+ Long newWorkflowId = _idMapping.get(oldWorkflowId);
+ _nodeStack.peek().put(_NEW_WORKFLOW_ID, newWorkflowId);
+ break;
+ case IN_EXPORT_DATE_PROPERTY:
+ try
+ {
+ ValueFactory vf = _parentNode.getSession().getValueFactory();
+ _exportDate = ValueHelper.convert(value, PropertyType.DATE, vf).getDate().getTime();
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Unable to convert export date property";
+ _logger.error(msg, e);
+ throw new SAXException(msg, e);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Retrieves the exportDate.
+ * @return the exportDate
+ */
+ public Date getExportDate()
+ {
+ return _exportDate;
+ }
+
+ /**
+ * Retrieves the imported node.
+ * Should only be called after the import has been done.
+ * @return The imported node.
+ * @throws RepositoryException
+ */
+ public Node getImportedNode() throws RepositoryException
+ {
+ if (_importedNodeName != null)
+ {
+ return _parentNode.getNode(_importedNodeName);
+ }
+
+ return null;
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/handlers/ExportAmetysObjectHandler.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/handlers/ExportAmetysObjectHandler.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/handlers/ExportAmetysObjectHandler.java (revision 0)
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.handlers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.cms.io.IOHandler;
+import org.ametys.cms.io.exporters.LogExporter;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+
+/**
+ * "Proxy" handler used to export an ametys object.
+ */
+public class ExportAmetysObjectHandler extends IOHandler
+{
+ /** In skipped node */
+ protected static final int IN_SKIPPED_NODE = 1;
+
+ /** Ametys workflowRef property */
+ protected static final int IN_WORKFLOWREF_PROPERTY = 12;
+
+ /** references property */
+ protected static final String _REFERENCES_PROPERTY = "references";
+
+ /** Set of the collected workflow references */
+ protected final Set _workflowRefs = new HashSet();
+
+ /** MultiMap that maps node relative path to the name of their binary properties */
+ protected final ListMultimap _binaryProperties = ArrayListMultimap.create();
+
+ /** Set of the collected ressources by path */
+ protected final Set _resources = new HashSet();
+
+ /** MultiMap of the collected external references, keyed by node identifier */
+ protected final ListMultimap _externalReferences = ArrayListMultimap.create();
+
+ /** List of property types that denote a reference */
+ protected final List _referenceTypes = Arrays.asList(
+ PropertyType.TYPENAME_REFERENCE,
+ PropertyType.TYPENAME_WEAKREFERENCE,
+ PropertyType.TYPENAME_PATH);
+
+ /** List of node names that may not be exported, must be populated in the constructor if needed */
+ protected final List _nodesToSkip = new ArrayList();
+
+ /** An internal counter helper */
+ protected int _cpt;
+
+ /**
+ * Ctor inheritance
+ * @param contentHandler The {@link ContentHandler} to be wrapped.
+ * @param parentNode The node from which the import/export is relative
+ */
+ public ExportAmetysObjectHandler(final ContentHandler contentHandler, Node parentNode)
+ {
+ super(contentHandler, parentNode);
+ }
+
+ @Override
+ protected void _onNodeStart(Attributes atts) throws SAXException
+ {
+ switch (_state)
+ {
+ case IN_NODE:
+ String name = atts.getValue(IOConstants.SV_NAME);
+ if (_nodesToSkip.contains(name))
+ {
+ boolean skipExport = _onNodeToSkip(name);
+
+ if (skipExport)
+ {
+ _state = IN_SKIPPED_NODE;
+ _cpt++;
+
+ // Stop forwarding events
+ _flags &= ~(FORWARD_STARTS | FORWARD_ENDS | FORWARD_CHARACTERS);
+ }
+ }
+ break;
+ case IN_SKIPPED_NODE:
+ _cpt++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * This method is used to confirm that a node must be skipped. Specific processing can also be done here.
+ * @param name The name of the node
+ * @return true to confirme that the node will be skipped (ie. not exported).
+ * @throws SAXException
+ */
+ protected boolean _onNodeToSkip(String name) throws SAXException
+ {
+ return true;
+ }
+
+ @Override
+ protected void _onPropertyStart(Attributes atts)
+ {
+ switch (_state)
+ {
+ case IN_NODE:
+ String type = atts.getValue(IOConstants.SV_TYPE);
+ String name = atts.getValue(IOConstants.SV_NAME);
+
+ if (IOConstants.WORKFLOW_REF_PROPERTY_NAME.equals(name))
+ {
+ _state = IN_WORKFLOWREF_PROPERTY;
+ }
+ else if (PropertyType.TYPENAME_BINARY.equals(type))
+ {
+ String path = (String) _nodeStack.peek().get(PATH_PROPERTY_NAME);
+ _binaryProperties.put(path, name);
+ }
+ else if (_referenceTypes.contains(type) && !name.startsWith(Name.NS_JCR_PREFIX + ':'))
+ {
+ Properties props = _nodeStack.peek();
+
+ List references = (List) props.get(_REFERENCES_PROPERTY);
+ if (references == null)
+ {
+ references = new ArrayList();
+ props.put(_REFERENCES_PROPERTY, references);
+ }
+ references.add(name);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onValueStart(Attributes atts)
+ {
+ switch (_state)
+ {
+ case IN_WORKFLOWREF_PROPERTY:
+ // Set the scan characters flag
+ _flags |= SCAN_CHARACTERS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onNodeEnd() throws SAXException
+ {
+ switch (_state)
+ {
+ case IN_NODE:
+ // Populate external references
+ Properties props = _nodeStack.peek();
+ List references = (List) props.get(_REFERENCES_PROPERTY);
+ String path = (String) props.get(PATH_PROPERTY_NAME);
+ try
+ {
+ path = StringUtils.removeStart(path, _parentNode.getName() + '/');
+ _populateExternalReferences(references, path);
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Error during the export of the node '" + props.get(NAME_PROPERTY_NAME) + "'.";
+ _logger.error(msg, e);
+ throw new SAXException(msg, e);
+ }
+ break;
+ case IN_SKIPPED_NODE:
+ _cpt--;
+
+ if (_cpt == 0)
+ {
+ _state = IN_NODE;
+
+ // Start forwarding events
+ _flags |= FORWARD_STARTS | FORWARD_ENDS | FORWARD_CHARACTERS;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Populate external references multimap.
+ * @param referenceProperties
+ * @param relPath Path to the current node, relative to the export base node.
+ * @throws RepositoryException
+ */
+ private void _populateExternalReferences(List referenceProperties, String relPath) throws RepositoryException
+ {
+ if (referenceProperties == null || referenceProperties.isEmpty())
+ {
+ return;
+ }
+
+ Node node = _parentNode.getNode(relPath);
+ for (String refProperty : referenceProperties)
+ {
+ Property property = node.getProperty(refProperty);
+ _addExternalReferences(node, property);
+ }
+ }
+
+
+ /**
+ * Add external references
+ * @param node
+ * @param property
+ * @throws ValueFormatException
+ * @throws RepositoryException
+ */
+ private void _addExternalReferences(Node node, Property property) throws RepositoryException
+ {
+ if (property.isMultiple())
+ {
+ for (Value value : property.getValues())
+ {
+ _addExternalReference(node, property, value);
+ }
+ }
+ else
+ {
+ _addExternalReference(node, property, property.getValue());
+ }
+ }
+
+ /**
+ * Add external reference
+ * @param node
+ * @param property
+ * @param value
+ * @throws RepositoryException
+ */
+ private void _addExternalReference(Node node, Property property, Value value) throws RepositoryException
+ {
+ int propType = property.getType();
+ switch (propType)
+ {
+ case PropertyType.REFERENCE:
+ _addExternalReferenceReferenceType(node, property, value);
+ break;
+ case PropertyType.WEAKREFERENCE:
+ _addExternalReferenceWeakReferenceType(node, property, value);
+ break;
+ case PropertyType.PATH:
+ _addExternalReferencePathType(node, property, value);
+ break;
+ default:
+ // should not happen.
+ _logger.warn("unexpected property type");
+ }
+ }
+
+ /**
+ * Add an external reference of type REFERENCE to the
+ * {@link #_externalReferences} MultiMap if needed.
+ *
+ * @param node
+ * @param property
+ * @param value
+ * @throws RepositoryException
+ */
+ private void _addExternalReferenceReferenceType(Node node, Property property, Value value) throws RepositoryException
+ {
+ Node referencedNode = null;
+ try
+ {
+ referencedNode = node.getSession().getNodeByIdentifier(value.getString());
+ }
+ catch (ItemNotFoundException e)
+ {
+ // Should not occurs
+ String msg = "Node '" + node.getName() + "' has a property of type '" + PropertyType.TYPENAME_REFERENCE + "' with value '" + value.getString()
+ + "' but the referenced node do not exists.";
+ _logger.error(msg, e);
+ throw e;
+ }
+
+ _populateExternalReferences(node, property, value, PropertyType.TYPENAME_REFERENCE, referencedNode);
+ }
+
+ /**
+ * Add an external reference of type WEAKREFERENCE to the
+ * {@link #_externalReferences} MultiMap if needed.
+ *
+ * @param node
+ * @param property
+ * @param value
+ * @throws RepositoryException
+ * @throws RepositoryException
+ */
+ private void _addExternalReferenceWeakReferenceType(Node node, Property property, Value value) throws RepositoryException
+ {
+ Node referencedNode = null;
+
+ try
+ {
+ referencedNode = node.getSession().getNodeByIdentifier(value.getString());
+ }
+ catch (ItemNotFoundException e)
+ {
+ String msg = "Node '" + node.getName() + "' has a property of type '" + PropertyType.TYPENAME_WEAKREFERENCE + "' with value '" + value.getString()
+ + "' and the referenced node no longer exists.";
+ _logger.warn(msg, e);
+ return;
+ }
+
+ _populateExternalReferences(node, property, value, PropertyType.TYPENAME_WEAKREFERENCE, referencedNode);
+ }
+
+ /**
+ * Add an external reference of type PATH to the
+ * {@link #_externalReferences} MultiMap if needed.
+ *
+ * @param node
+ * @param property
+ * @param value
+ * @throws RepositoryException
+ * @throws RepositoryException
+ */
+ private void _addExternalReferencePathType(Node node, Property property, Value value) throws RepositoryException
+ {
+ Item referencedItem = null;
+
+ Session session = node.getSession();
+ if (!(session instanceof SessionImpl))
+ {
+ String msg = "Cannot test if a property of type '" + PropertyType.TYPENAME_PATH + " of the node '" + node.getName()
+ + "' reference an item which is outside the scope of the site.";
+ _logger.warn(msg);
+ return;
+ }
+
+ String path = value.getString();
+ Path p = ((SessionImpl) session).getQPath(path);
+
+ boolean absolute = p.isAbsolute();
+ try
+ {
+ referencedItem = absolute ? session.getNode(path) : property.getParent().getNode(path);
+ }
+ catch (PathNotFoundException e)
+ {
+ try
+ {
+ referencedItem = absolute ? session.getProperty(path) : property.getParent().getProperty(path);
+ }
+ catch (PathNotFoundException e2)
+ {
+ String msg = "Node '" + node.getName() + "' has a property of type '" + PropertyType.TYPENAME_PATH + "' with value '" + value.getString()
+ + "' and the referenced item no longer exists.";
+ _logger.warn(msg, e);
+ return;
+ }
+ }
+
+ _populateExternalReferences(node, property, value, PropertyType.TYPENAME_PATH, referencedItem);
+ }
+
+ /**
+ * Populate the external references multimap with a new value.
+ * @param node
+ * @param property
+ * @param value
+ * @param referencedItem
+ * @param refType
+ * @throws RepositoryException
+ */
+ private void _populateExternalReferences(Node node, Property property, Value value, String refType, Item referencedItem) throws RepositoryException
+ {
+ // To be considered as external, referenced item should not belong to the site.
+ if (_isExternal(referencedItem))
+ {
+ Properties props = new Properties();
+ props.put(LogExporter.EXTERNAL_REF_PROPERTY_NODE_NAME, node.getName());
+ props.put(LogExporter.EXTERNAL_REF_PROPERTY_NODE_PATH, node.getPath());
+ props.put(LogExporter.EXTERNAL_REF_PROPERTY_NAME, property.getName());
+ props.put(LogExporter.EXTERNAL_REF_PROPERTY_TYPE, refType);
+ props.put(LogExporter.EXTERNAL_REF_PROPERTY_VALUE, value.getString());
+ _externalReferences.put(node.getIdentifier(), props);
+ }
+ }
+
+ /**
+ * This method must indicate is the referenced item passed as an argument is
+ * external (out of the scope of the export).
+ *
+ * @param referencedItem The item
+ * @return true to confirme that the node will be skipped (ie. not
+ * exported).
+ * @throws RepositoryException
+ */
+ protected boolean _isExternal(Item referencedItem) throws RepositoryException
+ {
+ return false;
+ }
+
+ @Override
+ protected void _onPropertyEnd()
+ {
+ switch (_state)
+ {
+ case IN_WORKFLOWREF_PROPERTY:
+ _state = IN_NODE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onValueEnd(String value)
+ {
+ switch (_state)
+ {
+ case IN_WORKFLOWREF_PROPERTY:
+ if (value != null)
+ {
+ _workflowRefs.add(value);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Getters
+ /**
+ * Retrieves the workflow identifiers.
+ * @return the workflow identifiers
+ */
+ public Set getWorkflowRefs()
+ {
+ return _workflowRefs;
+ }
+
+ /**
+ * Retrieves the binary properties.
+ * @return the binaryProperties
+ */
+ public Map> getBinaryProperties()
+ {
+ return _binaryProperties.asMap();
+ }
+
+ /**
+ * Retrieves the resources.
+ * @return the resources
+ */
+ public Set getResources()
+ {
+ return _resources;
+ }
+
+ /**
+ * Retrieves the external references
+ * @return the externalReferences
+ */
+ public Map> getExternalReferences()
+ {
+ return _externalReferences.asMap();
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/handlers/ImportWorkflowHandler.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/handlers/ImportWorkflowHandler.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/handlers/ImportWorkflowHandler.java (revision 0)
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.handlers;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.apache.cocoon.xml.AttributesImpl;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.cms.io.IOHandler;
+import org.ametys.cms.io.IOUtils;
+import org.ametys.cms.io.importers.ImportReferenceTracker;
+
+/**
+ * "Proxy" handler used to import a workflow.
+ * Import workflow entry with new (unused) id and collect information.
+ */
+public class ImportWorkflowHandler extends IOHandler
+{
+ /** In entry node state */
+ protected static final int IN_ENTRY_NODE = 1;
+
+ /** Do not forward event to superclass mode. */
+ protected static final int _DO_NOT_FORWARD_EVENTS_MODE = -1;
+
+ /** In oswf:id property of an entry node */
+ protected static final int IN_ENTRY_NODE_ID_PROPERTY = 12;
+
+ /** uuid property name */
+ protected static final String _UUID_PROPERTY_NAME = "uuid";
+
+ /** imported node is an oswf:entry */
+ protected static final String _IS_ENTRY_NODE_PROPERTY = "isEntryNode";
+
+ /** old entry id property */
+ protected static final String _OLD_ENTRY_ID_PROPERTY = "oldEntryId";
+
+ /** Internal node depth counter */
+ protected int _nodeCpt;
+
+ /** The oswf:root workflow node */
+ protected Node _rootWorkflowNode;
+
+ /** The reference tracker to populate during import */
+ protected ImportReferenceTracker _referenceTracker;
+
+ /** Mapping of old entry id to new entry id */
+ protected Map _idMapping = new HashMap();
+
+ /** The value of the next entry id property */
+ protected long _currentEntryId;
+
+ /** The JCR uuidBehavior used for the import */
+ protected int _uuidBehavior;
+
+ /**
+ * Ctor inheritance
+ * @param referenceTracker
+ * @param rootWorkflowNode
+ * @param uuidBehavior
+ * @param nextEntryId the value of the oswf root node next entry id property
+ */
+ public ImportWorkflowHandler(Node rootWorkflowNode, int uuidBehavior, ImportReferenceTracker referenceTracker, long nextEntryId)
+ {
+ super(new DefaultHandler(), null);
+ _rootWorkflowNode = rootWorkflowNode;
+ _uuidBehavior = uuidBehavior;
+ _referenceTracker = referenceTracker;
+ _currentEntryId = nextEntryId - 1;
+ _state = _DO_NOT_FORWARD_EVENTS_MODE;
+ }
+
+ @Override
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
+ {
+ if (IOConstants.SV_NODE.equals(qName))
+ {
+ if (_state == _DO_NOT_FORWARD_EVENTS_MODE)
+ {
+ _state = IN_NODE;
+ _nodeCpt = 0;
+ _startWorkflowEntryImport();
+ }
+
+ _nodeCpt++;
+ }
+
+ if (_state != _DO_NOT_FORWARD_EVENTS_MODE)
+ {
+ super.startElement(namespaceURI, localName, qName, atts);
+ }
+ }
+
+ @Override
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException
+ {
+ if (_state != _DO_NOT_FORWARD_EVENTS_MODE)
+ {
+ super.endElement(namespaceURI, localName, qName);
+
+ if (IOConstants.SV_NODE.equals(qName))
+ {
+ _nodeCpt--;
+
+ if (_nodeCpt == 0)
+ {
+ _state = _DO_NOT_FORWARD_EVENTS_MODE;
+ _endWorkflowEntryImport();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException
+ {
+ if (_state != _DO_NOT_FORWARD_EVENTS_MODE)
+ {
+ super.characters(ch, start, length);
+ }
+ }
+
+ /**
+ * Starts the JCR import of a new workflow node (oswf:entry)
+ * @throws SAXException
+ */
+ protected void _startWorkflowEntryImport() throws SAXException
+ {
+ // Increment oswf entry id.
+ _currentEntryId++;
+
+ try
+ {
+ _parentNode = _getWorkflowEntryParentNode(_currentEntryId);
+ setContentHandler(_rootWorkflowNode.getSession().getImportContentHandler(_parentNode.getPath(), _uuidBehavior));
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Fatal error when trying to import a new workflow entry with id '" + "" + "'.";
+ _logger.error(msg);
+ throw new SAXException(msg, e);
+ }
+
+ startDocument(); // start workflow entry import
+ }
+
+ /**
+ * Retrieves the parent node of a workflow node.
+ * Create the necessary nodes if not existing.
+ * @param entryId the id of the node
+ * @return The parent node (an oswf:hashSegment)
+ * @throws RepositoryException
+ */
+ protected Node _getWorkflowEntryParentNode(Long entryId) throws RepositoryException
+ {
+ // Generate hash for this entry id.
+ List hashSegments = IOUtils.getHashedName(Long.toString(entryId));
+
+ // Balanced tree management
+ Node htree = _rootWorkflowNode;
+ for (String hashSegment : hashSegments)
+ {
+ if (htree.hasNode(hashSegment))
+ {
+ // Go down to a leaf of the hash tree
+ htree = htree.getNode(hashSegment);
+ }
+ else
+ {
+ // Add a new leaf to the hash tree
+ htree = htree.addNode(hashSegment, IOConstants.OSWF_HTREE_NT);
+ }
+ }
+
+ return htree;
+ }
+
+ /**
+ * Ends the JCR import of a new workflow node (oswf:entry)
+ * @throws SAXException
+ */
+ protected void _endWorkflowEntryImport() throws SAXException
+ {
+ endDocument(); // end of workflow entry import.
+
+ setContentHandler(new DefaultHandler());
+ _parentNode = null;
+ }
+
+ @Override
+ protected void _onNodeStart(Attributes atts) throws SAXException
+ {
+ _state = IN_NODE;
+
+ // In an entry node, dynamically change the value of the entry id.
+ String name = atts.getValue(IOConstants.SV_NAME);
+ if (name.startsWith(IOConstants.ENTRY_NODE_PREFIX))
+ {
+ _state = IN_ENTRY_NODE;
+
+ Properties props = _nodeStack.peek();
+ props.put(_IS_ENTRY_NODE_PROPERTY, true);
+
+ AttributesImpl newAtts = new AttributesImpl(atts);
+ String newName = IOConstants.ENTRY_NODE_PREFIX + _currentEntryId;
+ newAtts.setValue(atts.getIndex(IOConstants.SV_NAME), newName);
+
+ // update path property (no need to count children, because oswf:entry name are uniques)
+ _path.pop();
+ _path.push(newName);
+ props.put(PATH_PROPERTY_NAME, _getCurrentPath());
+ props.put(NAME_PROPERTY_NAME, newName);
+
+ Name svNodeName = NameConstants.SV_NODE;
+ sendStartElement(svNodeName.getNamespaceURI(), svNodeName.getLocalName(), IOConstants.SV_NODE, newAtts);
+
+ // Do not forward the very next start event.
+ _flags |= DO_NOT_FORWARD_NEXT_START;
+ }
+ }
+
+ @Override
+ protected void _onPropertyStart(Attributes atts) throws SAXException
+ {
+ String name = atts.getValue(IOConstants.SV_NAME);
+
+ // Collecting the jcr:uuid of the current node
+ if ("jcr:uuid".equals(name))
+ {
+ _state = IN_JCR_UUID_PROPERTY;
+ }
+ else if (IOConstants.ENTRY_NODE_ID_PROPERTY.equals(name) && _state == IN_ENTRY_NODE)
+ {
+ _state = IN_ENTRY_NODE_ID_PROPERTY;
+ }
+ }
+
+ @Override
+ protected void _onValueStart(Attributes atts) throws SAXException
+ {
+ switch (_state)
+ {
+ case IN_JCR_UUID_PROPERTY:
+ // Set the scan characters flag
+ _flags |= SCAN_CHARACTERS;
+ break;
+ case IN_ENTRY_NODE_ID_PROPERTY:
+ Name svValueName = NameConstants.SV_VALUE;
+ sendStartElement(svValueName.getNamespaceURI(), svValueName.getLocalName(), IOConstants.SV_VALUE, atts);
+
+ char[] entryId = Long.toString(_currentEntryId).toCharArray();
+ sendCharacters(entryId, 0, entryId.length);
+
+ // Set the scan characters and do not forward next start flags
+ _flags |= SCAN_CHARACTERS | DO_NOT_FORWARD_NEXT_START;
+ // Stop forwarding characters events
+ _flags &= ~FORWARD_CHARACTERS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onNodeEnd() throws SAXException
+ {
+ Properties props = _nodeStack.peek();
+
+ // 1 - Retrieves imported node from path
+ String relPath = (String) props.get(PATH_PROPERTY_NAME);
+ if (relPath == null)
+ {
+ return;
+ }
+
+ try
+ {
+ Node node = _parentNode.getNode(relPath);
+
+ // 2 - Analyze primary type
+ if (!node.isNodeType(IOConstants.OSWF_ENTRY_NT))
+ {
+ return;
+ }
+
+ // 3 - Checks if its uuid has changed
+ String oldUUID = (String) props.get(_UUID_PROPERTY_NAME);
+ if (oldUUID == null)
+ {
+ String msg = "Expecting an UUID property for this node.";
+ _logger.error(msg);
+ throw new SAXException(msg);
+ }
+
+ String newUUID = node.getIdentifier();
+ if (!newUUID.equals(oldUUID))
+ {
+ _referenceTracker.putUUIDChange(oldUUID, newUUID);
+ }
+
+ // 4 - Add reference to track
+ try
+ {
+ Property contentRef = node.getProperty(IOConstants.CONTENT_REF_PROPERTY_NAME);
+ _referenceTracker.tracksContentRef(contentRef);
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Expecting a '" + IOConstants.CONTENT_REF_PROPERTY_NAME + "' property for this node.";
+ _logger.error(msg);
+ throw e;
+ }
+
+ // 5 - Add the entry id mapping
+ Long oldEntryId = (Long) props.get(_OLD_ENTRY_ID_PROPERTY);
+ Long newEntryId = node.getProperty(IOConstants.ENTRY_NODE_ID_PROPERTY).getLong();
+ _idMapping.put(oldEntryId, newEntryId);
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Error during the post processing step of the import of a node.";
+ _logger.error(msg, e);
+ throw new SAXException(msg, e);
+ }
+ }
+
+ @Override
+ protected void _onPropertyEnd() throws SAXException
+ {
+ Boolean isEntryNode = (Boolean) _nodeStack.peek().get(_IS_ENTRY_NODE_PROPERTY);
+ if (BooleanUtils.isTrue(isEntryNode))
+ {
+ _state = IN_ENTRY_NODE;
+ }
+ else
+ {
+ _state = IN_NODE;
+ }
+ }
+
+ @Override
+ protected void _onValueEnd(String value) throws SAXException
+ {
+ switch (_state)
+ {
+ case IN_JCR_UUID_PROPERTY:
+ _nodeStack.peek().put(_UUID_PROPERTY_NAME, value);
+ break;
+ case IN_ENTRY_NODE_ID_PROPERTY:
+ Long oldId = Long.valueOf(value);
+ _nodeStack.peek().put(_OLD_ENTRY_ID_PROPERTY, oldId);
+ // Start forwarding characters events again
+ _flags |= FORWARD_CHARACTERS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Retrieves the currentEntryId.
+ * @return the currentEntryId
+ */
+ public long getCurrentEntryId()
+ {
+ return _currentEntryId;
+ }
+
+ /**
+ * Retrieves the idMapping.
+ * @return the idMapping
+ */
+ public Map getIdMapping()
+ {
+ return _idMapping;
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/handlers/ExportResourcesHandler.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/handlers/ExportResourcesHandler.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/handlers/ExportResourcesHandler.java (revision 0)
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io.handlers;
+
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.cms.io.IOHandler;
+import org.ametys.plugins.explorer.resources.jcr.JCRResourceFactory;
+import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory;
+
+/**
+ * "Proxy" handler used to collects useful information while exporting the resources of a site.
+ */
+public class ExportResourcesHandler extends IOHandler
+{
+ /** primary type property name */
+ protected static final String _PRIMARY_TYPE_PROPERTY_NAME = "primaryType";
+
+ /** Set tracking resources that should be imported */
+ private final Set _resources = new HashSet();
+
+ /**
+ * Ctor inheritance
+ * @param contentHandler The {@link ContentHandler} to be wrapped.
+ * @param parentNode The node from which the import/export is relative
+ */
+ public ExportResourcesHandler(final ContentHandler contentHandler, Node parentNode)
+ {
+ super(contentHandler, parentNode);
+ }
+
+ @Override
+ protected void _onNodeStart(Attributes atts)
+ {
+ // nothing to do.
+ }
+
+ @Override
+ protected void _onPropertyStart(Attributes atts)
+ {
+ String name = atts.getValue(IOConstants.SV_NAME);
+
+ if (JcrConstants.JCR_PRIMARYTYPE.equals(name))
+ {
+ _state = IN_JCR_PRIMARY_TYPE_PROPERTY;
+ }
+ }
+
+ @Override
+ protected void _onValueStart(Attributes atts)
+ {
+ switch (_state)
+ {
+ case IN_JCR_PRIMARY_TYPE_PROPERTY:
+ // Set the scan characters flag
+ _flags |= SCAN_CHARACTERS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onNodeEnd() throws SAXException
+ {
+ // Retrieves the node properties.
+ Properties props = _nodeStack.peek();
+
+ // Retrieves primary type
+ String primaryType = (String) props.get(_PRIMARY_TYPE_PROPERTY_NAME);
+ if (primaryType == null)
+ {
+ return;
+ }
+
+ // Populate the resource map
+ boolean isResourceFile = false;
+ boolean isResourceDir = false;
+ if (JCRResourceFactory.RESOURCES_NODETYPE.equals(primaryType))
+ {
+ isResourceFile = true;
+
+ }
+ else if (JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE.equals(primaryType))
+ {
+ isResourceDir = true;
+ }
+
+ if (isResourceFile || isResourceDir)
+ {
+ String excludedBasePath = null;
+ try
+ {
+ excludedBasePath = _parentNode.getName() + '/';
+ }
+ catch (RepositoryException e)
+ {
+ String msg = "Error during the export of the resource '" + props.get(NAME_PROPERTY_NAME) + "' node.";
+ _logger.error(msg, e);
+ throw new SAXException(msg, e);
+ }
+
+ // Remove node that path are contained in the excluded base path.
+ String path = (String) props.get(PATH_PROPERTY_NAME);
+ if (excludedBasePath.startsWith(path))
+ {
+ return;
+ }
+
+ // Remove excluded base path from the path
+ path = StringUtils.removeStart(path, excludedBasePath);
+
+ // Suffix with '/' if is directory
+ if (isResourceDir)
+ {
+ path += '/';
+ }
+
+ _resources.add(path);
+ }
+ }
+
+ @Override
+ protected void _onPropertyEnd()
+ {
+ switch (_state)
+ {
+ case IN_JCR_PRIMARY_TYPE_PROPERTY:
+ _state = IN_NODE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void _onValueEnd(String value)
+ {
+ switch (_state)
+ {
+ case IN_JCR_PRIMARY_TYPE_PROPERTY:
+ if (value != null)
+ {
+ _nodeStack.peek().put(_PRIMARY_TYPE_PROPERTY_NAME, value);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Retrieves the resources.
+ * @return the resources
+ */
+ public Set getResources()
+ {
+ return _resources;
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/IOConstants.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/IOConstants.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/IOConstants.java (revision 0)
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io;
+
+import org.ametys.plugins.repository.RepositoryConstants;
+
+/**
+ * Collection of constants used in the import/export
+ */
+public final class IOConstants
+{
+ // System view elements
+ /** system view prefix */
+ public static final String SV_PREFIX = "sv:";
+ /** sv:node node */
+ public static final String SV_NODE = SV_PREFIX + "node";
+ /** sv:property node */
+ public static final String SV_PROPERTY = SV_PREFIX + "property";
+ /** sv:value node */
+ public static final String SV_VALUE = SV_PREFIX + "value";
+ /** sv:name attribute */
+ public static final String SV_NAME = SV_PREFIX + "name";
+ /** sv:type attribute */
+ public static final String SV_TYPE = SV_PREFIX + "type";
+
+ /** The Ametys internal workflow reference property name */
+ public static final String WORKFLOW_REF_PROPERTY_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowRef";
+ /** The Ametys internal content reference property name */
+ public static final String CONTENT_REF_PROPERTY_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contentRef";
+ /** The Ametys internal workflow id property name */
+ public static final String WORKFLOW_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowId";
+ /** The Ametys internal current step id property name */
+// public static final String CURRENT_STEP_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":currentStepId";
+ /** Constant for resources node name. */
+ public static final String RESOURCES_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources";
+ /** Constant for contents node name. */
+ public static final String CONTENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents";
+ /** The Ametys default content nodeType */
+ public static final String DEFAULT_CONTENT_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":defaultContent";
+
+ /** oswf prefix */
+ public static final String OSWF_PREFIX = "oswf:";
+ /** oswf entry node name prefix. */
+ public static final String OSWF_ROOT = OSWF_PREFIX + "root";
+ /** oswf entry node name prefix. */
+ public static final String OSWF_ROOT_NT = OSWF_PREFIX + "root";
+ /** oswf entry node type. */
+ public static final String OSWF_ENTRY_NT = OSWF_PREFIX + "entry";
+ /** Internal hash tree node type. */
+ public static final String OSWF_HTREE_NT = OSWF_PREFIX + "hashSegment";
+ /** ID property for oswf entries */
+ public static final String ENTRY_NODE_ID_PROPERTY = OSWF_PREFIX + "id";
+ /** Next entry id property for root oswf node. */
+ public static final String NEXT_ENTRY_ID_PROPERTY = OSWF_PREFIX + "nextEntryId";
+ /** oswf:currentStep node name */
+ public static final String OSWF_CURRENT_STEP = OSWF_PREFIX + "currentStep";
+ /** Entry node name prefix. */
+ public static final String ENTRY_NODE_PREFIX = "workflow-";
+
+ /** System view extension */
+ public static final String SYSTEM_VIEW_EXT = ".sv";
+ /** Archive workflows filename */
+ public static final String WORKFLOWS_FILE_NAME = "workflows" + SYSTEM_VIEW_EXT;
+ /** Archives resources filename */
+ public static final String RESOURCES_FILE_NAME = "resources" + SYSTEM_VIEW_EXT;
+
+ /** Archive data folder */
+ public static final String BINARY_DIR = "data/";
+ /** Archive archives data folder */
+ public static final String ARCHIVES_DIR = "archives/";
+ /** Archive explorer data folder */
+ public static final String EXPLORER_DIR = "explorer/";
+ /** Archive resources data folder */
+ public static final String RESOURCES_DIR = "_resources/";
+
+ /** Name of the root element in the workflow exported file */
+ public static final String WORKFLOW_FILE_ROOT_NODE = "oswfroot";
+
+ /**
+ * Private constructor to prevent instantiation of this class.
+ */
+ private IOConstants()
+ {
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/io/IOHandler.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/io/IOHandler.java (revision 0)
+++ main/plugin-cms/src/org/ametys/cms/io/IOHandler.java (revision 0)
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2012 Anyware Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ametys.cms.io;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.Properties;
+
+import javax.jcr.Node;
+
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.commons.lang.StringUtils;
+import org.apache.excalibur.xml.sax.ContentHandlerProxy;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.io.IOConstants;
+import org.ametys.runtime.util.LoggerFactory;
+
+/**
+ * Abstract base class for every handler used in the import/export
+ */
+public abstract class IOHandler extends ContentHandlerProxy
+{
+ // State management
+ /** In node state */
+ protected static final int IN_NODE = 0;
+
+ // Properties matched for a specific processing
+ /** JCR primary type property */
+ protected static final int IN_JCR_PRIMARY_TYPE_PROPERTY = 10;
+ /** JCR UUID property */
+ protected static final int IN_JCR_UUID_PROPERTY = 11;
+
+ // Set of handler flags used to control the behavior of the handler */
+ /** SAX start events are forwarded when set */
+ protected static final int FORWARD_STARTS = 1 << 0;
+
+ /** SAX end events are forwarded when set */
+ protected static final int FORWARD_ENDS = 1 << 1;
+
+ /** Received characters data are forwarded when true */
+ protected static final int FORWARD_CHARACTERS = 1 << 2;
+
+ /** received characters data are scanned when set */
+ protected static final int SCAN_CHARACTERS = 1 << 3;
+
+ /** The very next SAX start event will not be are forwarded when set */
+ protected static final int DO_NOT_FORWARD_NEXT_START = 1 << 4;
+
+ /** Default flag options */
+ protected static final int DEFAULT_FLAGS = FORWARD_STARTS | FORWARD_ENDS | FORWARD_CHARACTERS;
+
+ /** path property name */
+ protected static final String PATH_PROPERTY_NAME = "path";
+
+ /** name property name */
+ protected static final String NAME_PROPERTY_NAME = "name";
+
+ /** children property name (private) */
+ private static final String __CHILDREN_PROPERTY_NAME = "children";
+
+ /** avalon logger */
+ protected final Logger _logger = LoggerFactory.getLoggerFor(IOHandler.class);
+
+ /** Flag holder */
+ protected int _flags = DEFAULT_FLAGS;
+
+ /** The current state */
+ protected int _state = IN_NODE;
+
+ /** Deque that represent the current path relative to the base import node */
+ protected final Deque _path = new ArrayDeque();
+
+ /**
+ * Stack of the currently processed nodes. For each node, some properties
+ * are collected and stocked into this stack.
+ */
+ protected final Deque _nodeStack = new ArrayDeque();
+
+ /** The node from which the import/export is relative */
+ protected Node _parentNode;
+
+ /** An internal {@link StringBuilder} used to scan property values when needed */
+ protected StringBuilder _sb;
+
+ /**
+ * Ctor inheritance
+ * @param contentHandler The {@link ContentHandler} to be wrapped.
+ * @param parentNode The node from which the import/export is relative
+ */
+ public IOHandler(final ContentHandler contentHandler, Node parentNode)
+ {
+ super(contentHandler);
+ _parentNode = parentNode;
+ }
+
+ @Override
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
+ {
+ // In 'node' node, keep tracking of current path.
+ if (IOConstants.SV_NODE.equals(qName))
+ {
+ _analyzeNodeOnStart(atts);
+ _onNodeStart(atts);
+ }
+ // In property node, analyze attributes of interest.
+ else if (IOConstants.SV_PROPERTY.equals(qName))
+ {
+ _onPropertyStart(atts);
+ }
+ // In value node, retrieves value of interest.
+ else if (IOConstants.SV_VALUE.equals(qName))
+ {
+ _onValueStart(atts);
+ if ((_flags & SCAN_CHARACTERS) == SCAN_CHARACTERS)
+ {
+ _sb = new StringBuilder();
+ }
+ }
+
+ if ((_flags & DO_NOT_FORWARD_NEXT_START) == DO_NOT_FORWARD_NEXT_START)
+ {
+ _flags &= ~DO_NOT_FORWARD_NEXT_START;
+ }
+ else if ((_flags & FORWARD_STARTS) == FORWARD_STARTS)
+ {
+ super.startElement(namespaceURI, localName, qName, atts);
+ }
+ }
+
+ /**
+ * Notify the start of a sv:node node.
+ * Analyze the sv:node on the start event, and initialize its properties.
+ * @param atts
+ * @throws SAXException
+ * @see ContentHandler#startElement(String, String, String, Attributes)
+ */
+ protected void _analyzeNodeOnStart(Attributes atts) throws SAXException
+ {
+ String name = atts.getValue(IOConstants.SV_NAME);
+ if (StringUtils.isNotEmpty(name))
+ {
+ // Reference this node as a new child
+ _addNewChildToCurrentNode(name);
+
+ // Update current _indexed_ path (which means, dealing with same-name siblings issue)
+ int count = _countCurrentChildrenWithName(name);
+ String indexedName = count <= 1 ? name : name + '[' + count + ']';
+ _path.push(indexedName);
+
+ // New properties for this node, push them into the stack.
+ Properties props = new Properties();
+ props.put(PATH_PROPERTY_NAME, _getCurrentPath());
+ props.put(NAME_PROPERTY_NAME, name);
+ _nodeStack.push(props);
+ }
+ else
+ {
+ String msg = "Attribute '" + IOConstants.SV_NAME + "' was expected but not found.";
+ _logger.error(msg);
+ throw new SAXException(msg);
+ }
+ }
+
+ /**
+ * Notify the start of a sv:node node.
+ * @param atts
+ * @throws SAXException
+ * @see ContentHandler#startElement(String, String, String, Attributes)
+ */
+ protected abstract void _onNodeStart(Attributes atts) throws SAXException;
+
+ /**
+ * Notify the start of a sv:property node.
+ * @param atts
+ * @throws SAXException
+ * @see ContentHandler#startElement(String, String, String, Attributes)
+ */
+ protected abstract void _onPropertyStart(Attributes atts) throws SAXException;
+
+ /**
+ * Notify the start of a sv:value node.
+ * @param atts
+ * @throws SAXException
+ * @see ContentHandler#startElement(String, String, String, Attributes)
+ */
+ protected abstract void _onValueStart(Attributes atts) throws SAXException;
+
+ @Override
+ public void characters (char[] ch, int start, int length) throws SAXException
+ {
+ if ((_flags & FORWARD_CHARACTERS) == FORWARD_CHARACTERS)
+ {
+ super.characters(ch, start, length);
+ }
+
+ if ((_flags & SCAN_CHARACTERS) == SCAN_CHARACTERS)
+ {
+ _sb.append(ch, start, length);
+ }
+ }
+
+ @Override
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException
+ {
+ if ((_flags & FORWARD_ENDS) == FORWARD_ENDS)
+ {
+ super.endElement(namespaceURI, localName, qName);
+ }
+
+ if (IOConstants.SV_NODE.equals(qName))
+ {
+ _onNodeEnd();
+ _path.pop();
+ _nodeStack.pop();
+ }
+ else if (IOConstants.SV_PROPERTY.equals(qName))
+ {
+ _onPropertyEnd();
+ }
+ // In value node, retrieves value of interest.
+ else if (IOConstants.SV_VALUE.equals(qName))
+ {
+ String value = null;
+ if ((_flags & SCAN_CHARACTERS) == SCAN_CHARACTERS)
+ {
+ if (_sb.length() > 0)
+ {
+ value = _sb.toString();
+ _sb.setLength(0);
+ }
+
+ // Remove scan characters flag.
+ _flags &= ~SCAN_CHARACTERS;
+ }
+ _onValueEnd(value);
+ }
+ }
+
+ /**
+ * Notify the end of a sv:node node.
+ * @throws SAXException
+ * @see ContentHandler#endElement(String, String, String)
+ */
+ protected abstract void _onNodeEnd() throws SAXException;
+
+ /**
+ * Notify the end of a sv:property node.
+ * @throws SAXException
+ * @see ContentHandler#endElement(String, String, String)
+ */
+ protected abstract void _onPropertyEnd() throws SAXException;
+
+ /**
+ * Notify the end of a sv:value node.
+ * @param value The scanned value. null if scan flag is not set or if the
+ * retrieved value if empty.
+ * @throws SAXException
+ * @see ContentHandler#endElement(String, String, String)
+ */
+ protected abstract void _onValueEnd(String value) throws SAXException;
+
+ /**
+ * Manually send a start element notification.
+ * @param namespaceURI
+ * @param localName
+ * @param qName
+ * @param atts
+ * @throws SAXException
+ */
+ protected void sendStartElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
+ {
+ super.startElement(namespaceURI, localName, qName, atts);
+ }
+
+ /**
+ * Manually send a characters notification.
+ * @param ch
+ * @param start
+ * @param length
+ * @throws SAXException
+ */
+ public void sendCharacters (char[] ch, int start, int length) throws SAXException
+ {
+ super.characters(ch, start, length);
+ }
+
+ /**
+ * Manually send an end element notification.
+ * @param namespaceURI
+ * @param localName
+ * @param qName
+ * @throws SAXException
+ */
+ protected void sendEndElement(String namespaceURI, String localName, String qName) throws SAXException
+ {
+ super.endElement(namespaceURI, localName, qName);
+ }
+
+ /**
+ * Retrieves the path of the currently imported node.
+ * @return The retrieved path.
+ */
+ protected String _getCurrentPath()
+ {
+ return StringUtils.join(_path.descendingIterator(), '/');
+ }
+
+ /**
+ * Retrieves the currently imported/exported child nodes of the current node.
+ * @return the list of the children.
+ */
+ protected List _getCurrentNodeChildren()
+ {
+ if (_nodeStack.isEmpty())
+ {
+ return new ArrayList();
+ }
+
+ List children = (List) _nodeStack.peek().get(__CHILDREN_PROPERTY_NAME);
+ if (children == null)
+ {
+ return new ArrayList();
+ }
+
+ return children;
+ }
+
+ private void _addNewChildToCurrentNode(String name)
+ {
+ if (_nodeStack.isEmpty()) // if empty, we are importing the root node.
+ {
+ return;
+ }
+
+ List children = (List) _nodeStack.peek().get(__CHILDREN_PROPERTY_NAME);
+ if (children == null)
+ {
+ children = new ArrayList();
+ children.add(name);
+ _nodeStack.peek().put(__CHILDREN_PROPERTY_NAME, children);
+ }
+ else
+ {
+ children.add(name);
+ }
+ }
+
+ private int _countCurrentChildrenWithName(String name)
+ {
+ return Collections.frequency(_getCurrentNodeChildren(), name);
+ }
+}
Index: main/plugin-cms/src/org/ametys/cms/workflow/ValidationStepFunction.java
===================================================================
--- main/plugin-cms/src/org/ametys/cms/workflow/ValidationStepFunction.java (revision 19862)
+++ main/plugin-cms/src/org/ametys/cms/workflow/ValidationStepFunction.java (working copy)
@@ -15,7 +15,6 @@
*/
package org.ametys.cms.workflow;
-import java.util.List;
import java.util.Map;
import org.ametys.plugins.workflow.store.AmetysStep;
@@ -23,6 +22,7 @@
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.FunctionProvider;
import com.opensymphony.workflow.WorkflowException;
+import com.opensymphony.workflow.spi.Step;
/**
* OSWorkflow function for setting the "validate" flag on steps, indicating they represent a validation step.
@@ -33,11 +33,11 @@
@Override
public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
{
- List steps = (List) transientVars.get("currentSteps");
+ Step createdStep = (Step) transientVars.get("createdStep");
- for (AmetysStep step : steps)
+ if (createdStep != null && createdStep instanceof AmetysStep)
{
- step.setProperty("validation", true);
+ ((AmetysStep) createdStep).setProperty("validation", true);
}
}
}