Dave Ward 2c920d57cc Merged V4.1-BUG-FIX to HEAD
47423: Merged V4.1-BUG-FIX-2013_02_26 to V4.1-BUG-FIX
      47381: ALF-15903 : form.getFieldLabel(field.id) sometimes returns the wrong label
         Added a check for the overridden id with the ending "-cntrl"
   47424: Fixes: ALF-17950: Content I'm Editing dashlet runs relativeDate parsing twice. Removes duplicate code and also prevents relativeTime parsing from breaking if called multiple times.
   47425: Merged V4.1-BUG-FIX-2013_02_26 to V4.1-BUG-FIX
      47386: ALF-15873: Form field validators not executed for NON mandatory date fields
         Add to context all form constraints defined in custom config.
   47426: Merged V4.1-BUG-FIX-2013_02_26 to V4.1-BUG-FIX
      47418: ALF-16385 : When the 'My activites' dashlet is narrow enough, vertical sizing of the content box is wrong.
         Recalculate a height of dashlet, when it was resized
   47427: ALF-18092: fixed issue with hidden-transitions field JSON
   47428: Merge DEV to V4.1-BUG-FIX
     46336 : ALF-16747 changing type of the root node of replicated set of nodes is not propagated to target.
   47437: Fixes ALF-17145: Pagination did not play nicely with back button.
   47439: Implements suggested fix for: ALF-16603
   47443: Fixed ALF-17255: AUDIT_PATH_REGEX regex pattern recompiled at runtime 
    - Switch to pre-compiled Pattern
   47473: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2013_02_26 to BRANCHES/DEV/V4.1-BUG-FIX:
      47313: ALF-18006 : Sending a PUT request without a Content-Type header resets the contents mimetype to application/octet-stream
          fix unit test
   47475: ALF-18092: Fixed unit test fallout from hidden transitions property serialization changes


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@47476 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2013-03-03 12:36:29 +00:00

800 lines
28 KiB
Java

/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.audit.access;
import static org.alfresco.repo.audit.model.AuditApplication.AUDIT_PATH_SEPARATOR;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.repo.audit.model.AuditApplication;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCancelCheckOut;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckIn;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.OnCheckOut;
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
import org.alfresco.repo.copy.CopyServicePolicies.OnCopyCompletePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
import org.alfresco.repo.policy.PolicyScope;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.InvalidQNameException;
import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
/**
* Changes made to a {@code Node} in a single transaction. For example the creation of
* a Node also involves updating properties, but the main action remains create node.
*
* @author Alan Davis
*/
/*package*/ class NodeChange implements
BeforeDeleteNodePolicy, OnAddAspectPolicy, OnCreateNodePolicy, OnMoveNodePolicy,
OnRemoveAspectPolicy, OnUpdatePropertiesPolicy,
OnContentReadPolicy, OnContentUpdatePolicy,
OnCreateVersionPolicy,
OnCopyCompletePolicy,
OnCheckOut, OnCheckIn, OnCancelCheckOut
{
private static final String USER = "user";
private static final String ACTION = "action";
private static final String SUB_ACTIONS = "sub-actions";
private static final String NODE = "node";
private static final String PATH = "path";
private static final String TYPE = "type";
private static final String FROM = "from";
private static final String TO = "to";
private static final String ADD = "add";
private static final String DELETE = "delete";
private static final String COPY = "copy";
private static final String MOVE = "move";
private static final String PROPERTIES = "properties";
private static final String ASPECTS = "aspects";
private static final String VERSION_PROPERTIES = "version-properties";
public static final String SUB_ACTION = "sub-action";
private static final String DELETE_NODE = "deleteNode";
private static final String CREATE_NODE = "createNode";
private static final String MOVE_NODE = "moveNode";
private static final String UPDATE_NODE_PROPERTIES = "updateNodeProperties";
private static final String DELETE_NODE_ASPECT = "deleteNodeAspect";
private static final String ADD_NODE_ASPECT = "addNodeAspect";
private static final String CREATE_CONTENT = "createContent";
private static final String UPDATE_CONTENT = "updateContent";
private static final String READ_CONTENT = "readContent";
private static final String CREATE_VERSION = "createVersion";
private static final String COPY_NODE = "copyNode";
private static final String CHECK_IN = "checkIn";
private static final String CHECK_OUT = "checkOut";
private static final String CANCEL_CHECK_OUT = "cancelCheckOut";
private static final String INVALID_PATH_CHAR_REPLACEMENT = "-";
public static Collection<String> SUMMARY_KEYS = new ArrayList<String>();
static
{
SUMMARY_KEYS.add(buildPath(PROPERTIES, ADD));
SUMMARY_KEYS.add(buildPath(PROPERTIES, DELETE));
SUMMARY_KEYS.add(buildPath(PROPERTIES, FROM));
SUMMARY_KEYS.add(buildPath(PROPERTIES, TO));
SUMMARY_KEYS.add(buildPath(ASPECTS, ADD));
SUMMARY_KEYS.add(buildPath(ASPECTS, DELETE));
}
public static final String SUB_ACTION_PREFIX = SUB_ACTION + AUDIT_PATH_SEPARATOR;
private final NodeInfoFactory nodeInfoFactory;
private final NamespaceService namespaceService;
private NodeInfo nodeInfo;
private String action;
private boolean auditSubActions = false;
private Set<String> subActions = new LinkedHashSet<String>();
private List<Map<String, Serializable>> subActionAuditMaps;
private NodeInfo copyFrom;
private NodeInfo moveFrom;
private Map<QName, Serializable> fromProperties;
private Map<QName, Serializable> toProperties;
private HashSet<QName> addedAspects;
private HashSet<QName> deletedAspects;
private HashMap<String, Serializable> versionProperties;
/*package*/ NodeChange(NodeInfoFactory nodeInfoFactory, NamespaceService namespaceService, NodeRef nodeRef)
{
this.nodeInfoFactory = nodeInfoFactory;
this.nodeInfo = nodeInfoFactory.newNodeInfo(nodeRef);
this.namespaceService = namespaceService;
}
/**
* @return a derived action for a transaction based on the sub-actions that have taken place.
*/
public String getDerivedAction()
{
// Derive higher level action
String action;
if (subActions.contains(CHECK_OUT))
{
action = "CHECK OUT";
}
else if (subActions.contains(CHECK_IN))
{
action = "CHECK IN";
}
else if (subActions.contains(CANCEL_CHECK_OUT))
{
action = "CANCEL CHECK OUT";
}
else if (subActions.contains(COPY_NODE))
{
action = "COPY";
}
else if (subActions.contains(CREATE_NODE))
{
action = "CREATE";
}
else if (subActions.size() == 1 && subActions.contains(READ_CONTENT))
{
// Reads in combinations with other actions tend to only facilitate the other action.
action = "READ";
}
else if (subActions.contains(DELETE_NODE))
{
action = "DELETE";
}
else if (subActions.contains(CREATE_VERSION)) // && !subActions.contains(CREATE_NODE)
{
action = "CREATE VERSION";
}
else if (subActions.contains(UPDATE_CONTENT)) // && !subActions.contains(CREATE_NODE)
{
action = "UPDATE CONTENT";
}
else if (subActions.contains(MOVE_NODE))
{
action = "MOVE";
}
else
{
// Default to first sub-action
action = this.action;
}
return action;
}
/**
* @return {@code true} if the node has been created and then deleted.
*/
public boolean isTemporaryNode()
{
// No need to check the order as a new node would be given a ned node ref.
return subActions.contains(CREATE_NODE) && subActions.contains(DELETE_NODE);
}
private NodeChange setAction(String action)
{
this.action = action;
return this;
}
private void appendSubAction(NodeChange subNodeChange)
{
// Remember sub-actions so we can check them later to derive the high level action
subActions.add(subNodeChange.action);
// Default the action to the first sub-action;
if (action == null)
{
action = subNodeChange.action;
}
// Audit sub actions if required.
if (auditSubActions)
{
if (subActionAuditMaps == null)
{
subActionAuditMaps = new ArrayList<Map<String, Serializable>>();
}
subActionAuditMaps.add(subNodeChange.getAuditData(true));
}
}
public NodeChange setAuditSubActions(boolean auditSubActions)
{
this.auditSubActions = auditSubActions;
return this;
}
private NodeChange setCopyFrom(NodeRef copyFrom)
{
this.copyFrom = nodeInfoFactory.newNodeInfo(copyFrom);
return this;
}
private NodeChange setMoveFrom(ChildAssociationRef childAssocRef)
{
// Don't overwrite original value if multiple calls.
if (moveFrom == null)
{
moveFrom = nodeInfoFactory.newNodeInfo(childAssocRef);
}
return this;
}
private NodeChange setMoveTo(ChildAssociationRef childAssocRef)
{
nodeInfo = nodeInfoFactory.newNodeInfo(childAssocRef);
// Clear values if we are back to where we started.
if (nodeInfo.equals(moveFrom))
{
moveFrom = null;
}
return this;
}
private NodeChange setFromProperties(Map<QName, Serializable> fromProperties)
{
// Don't overwrite original value if multiple calls.
if (this.fromProperties == null)
{
this.fromProperties = fromProperties;
}
return this;
}
private NodeChange setToProperties(Map<QName, Serializable> toProperties)
{
this.toProperties = toProperties;
return this;
}
/**
* Add an aspect - if just deleted, remove the delete, otherwise record the add.
*/
private NodeChange addAspect(QName aspect)
{
if (addedAspects == null)
{
addedAspects = new HashSet<QName>();
}
// Consider sequences
// add = add
// del add = ---
// add del add = add
// add add = add
if (deletedAspects != null && deletedAspects.contains(aspect))
{
deletedAspects.remove(aspect);
}
else
{
addedAspects.add(aspect);
}
return this;
}
/**
* Delete an aspect - if just added, remove the add, otherwise record the delete.
*/
private NodeChange deleteAspect(QName aspect)
{
if (deletedAspects == null)
{
deletedAspects = new HashSet<QName>();
}
// Consider sequences
// del = del
// add del = ---
// del add del = del
// del del = del
if (addedAspects != null && addedAspects.contains(aspect))
{
addedAspects.remove(aspect);
}
else
{
deletedAspects.add(aspect);
}
return this;
}
private NodeChange setVersionProperties(
HashMap<String, Serializable> versionProperties)
{
if (this.versionProperties == null)
{
this.versionProperties = new HashMap<String, Serializable>();
}
this.versionProperties.putAll(versionProperties);
return this;
}
@Override
public void beforeDeleteNode(NodeRef nodeRef)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(DELETE_NODE));
}
@Override
public void onCreateNode(ChildAssociationRef childAssocRef)
{
NodeRef nodeRef = childAssocRef.getChildRef();
Map<QName, Serializable> fromProperties = Collections.<QName, Serializable>emptyMap();
Map<QName, Serializable> toProperties = nodeInfoFactory.getProperties(nodeRef);
this.fromProperties = null; // Sometimes onCreateNode policy is out of order (e.g. create a person)
setFromProperties(fromProperties);
setToProperties(toProperties);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(CREATE_NODE).
setFromProperties(fromProperties).
setToProperties(toProperties));
}
@Override
public void onMoveNode(ChildAssociationRef fromChildAssocRef, ChildAssociationRef toChildAssocRef)
{
setMoveFrom(fromChildAssocRef);
setMoveTo(toChildAssocRef);
// Note: A change of the child node name will be picked up as a property name change.
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, toChildAssocRef.getChildRef()).
setAction(MOVE_NODE).
setMoveFrom(fromChildAssocRef).
setMoveTo(toChildAssocRef));
}
@Override
public void onUpdateProperties(NodeRef nodeRef,
Map<QName, Serializable> fromProperties, Map<QName, Serializable> toProperties)
{
// Sometime we don't get the fromProperties, so just use the latest we have
if (fromProperties.isEmpty() && this.toProperties != null)
{
fromProperties = this.toProperties;
}
setFromProperties(fromProperties);
setToProperties(toProperties);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(UPDATE_NODE_PROPERTIES).
setFromProperties(fromProperties).
setToProperties(toProperties));
}
@Override
public void onRemoveAspect(NodeRef nodeRef, QName aspect)
{
deleteAspect(aspect);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(DELETE_NODE_ASPECT).
deleteAspect(aspect));
}
@Override
public void onAddAspect(NodeRef nodeRef, QName aspect)
{
addAspect(aspect);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(ADD_NODE_ASPECT).
addAspect(aspect));
}
@Override
public void onContentUpdate(NodeRef nodeRef, boolean newContent)
{
if (newContent)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(CREATE_CONTENT));
}
else
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(UPDATE_CONTENT));
}
}
@Override
public void onContentRead(NodeRef nodeRef)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(READ_CONTENT));
}
@Override
public void onCreateVersion(QName classRef, NodeRef nodeRef,
Map<String, Serializable> versionProperties, PolicyScope nodeDetails)
{
setVersionProperties((HashMap<String, Serializable>)versionProperties);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(CREATE_VERSION).
setVersionProperties((HashMap<String, Serializable>)versionProperties));
// Note nodeDetails are not used
}
public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef targetNodeRef,
boolean copyToNewNode, Map<NodeRef, NodeRef> copyMap)
{
setCopyFrom(sourceNodeRef);
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, targetNodeRef).
setAction(COPY_NODE).
setCopyFrom(sourceNodeRef));
}
public void onCheckOut(NodeRef workingCopy)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, workingCopy).
setAction(CHECK_OUT));
}
public void onCheckIn(NodeRef nodeRef)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(CHECK_IN));
}
public void onCancelCheckOut(NodeRef nodeRef)
{
appendSubAction(new NodeChange(nodeInfoFactory, namespaceService, nodeRef).
setAction(CANCEL_CHECK_OUT));
}
public Map<String, Serializable> getAuditData(boolean subAction)
{
Map<String, Serializable> auditMap = new HashMap<String, Serializable>(
2 *
(1 + // action
1 + // user
1 + // sub actions
3 + // node, path, type
3 + // copy source's node, path, type
3 + // move source's node, path, type
(fromProperties != null ? fromProperties.size() + toProperties.size() + 4 : 0) +
// individual property changes
// grouped from, to, add and delete changes
(addedAspects != null ? addedAspects.size() : 0) +
(deletedAspects != null ? deletedAspects.size() : 0) +
(versionProperties != null ? versionProperties.size()+1 : 0) +
getSubAuditDataSize()));
// For a transaction, set the action
if (!subAction)
{
setAction(getDerivedAction());
}
auditMap.put(ACTION, action);
if (!subAction) // no need to repeat for sub actions
{
auditMap.put(USER, AuthenticationUtil.getFullyAuthenticatedUser());
addSubActionsToAuditMap(auditMap);
auditMap.put(NODE, nodeInfo.getNodeRef());
auditMap.put(PATH, nodeInfo.getPrefixPath());
auditMap.put(TYPE, nodeInfo.getPrefixType());
}
if (copyFrom != null)
{
auditMap.put(buildPath(COPY, FROM, NODE), copyFrom.getNodeRef());
auditMap.put(buildPath(COPY, FROM, PATH), copyFrom.getPrefixPath());
auditMap.put(buildPath(COPY, FROM, TYPE), copyFrom.getPrefixType());
}
if (moveFrom != null)
{
auditMap.put(buildPath(MOVE, FROM, NODE), moveFrom.getNodeRef());
auditMap.put(buildPath(MOVE, FROM, PATH), moveFrom.getPrefixPath());
auditMap.put(buildPath(MOVE, FROM, TYPE), moveFrom.getPrefixType());
}
if (fromProperties != null)
{
addPropertyChangesToAuditMap(auditMap, subAction);
}
if (addedAspects != null && !addedAspects.isEmpty())
{
addAspectChangesToAuditMap(auditMap, ADD, addedAspects, subAction);
}
if (deletedAspects != null && !deletedAspects.isEmpty())
{
addAspectChangesToAuditMap(auditMap, DELETE, deletedAspects, subAction);
}
if (versionProperties != null && !versionProperties.isEmpty())
{
addVersionPropertiesToAuditMap(auditMap, versionProperties, subAction);
}
addSubActionAuditMapsToAuditMap(auditMap);
return auditMap;
}
private void addSubActionsToAuditMap(Map<String, Serializable> auditMap)
{
StringBuilder sb = new StringBuilder();
for (String subAction: subActions)
{
if (sb.length() > 0)
{
sb.append(' ');
}
sb.append(subAction);
}
auditMap.put(SUB_ACTIONS, sb.toString());
}
private void addPropertyChangesToAuditMap(Map<String, Serializable> auditMap, boolean subAction)
{
HashMap<QName, Serializable> from = new HashMap<QName, Serializable>(fromProperties.size());
HashMap<QName, Serializable> to = new HashMap<QName, Serializable>(toProperties.size());
HashMap<QName, Serializable> add = new HashMap<QName, Serializable>(toProperties.size());
HashMap<QName, Serializable> delete = new HashMap<QName, Serializable>(fromProperties.size());
// Initially check for changes to existing keys and values.
// Record individual value changes and group (from, to, delete) changes in their own maps.
for (Map.Entry<QName, Serializable> entry : fromProperties.entrySet())
{
// Audit QNames with the prefix set. The key can be used in original Set and
// Map operations as only the namesapace and local name are used in equals and
// hashcode methods.
QName key = getPrefixedQName(entry.getKey());
String name = replaceInvalidPathChars(key.toPrefixString());
Serializable beforeValue = entry.getValue();
Serializable afterValue = null;
boolean exists = toProperties.containsKey(key);
boolean same = false;
if (exists)
{
// Audit nothing if both values are null or equal.
afterValue = toProperties.get(key);
if ((beforeValue == afterValue) ||
(beforeValue != null && beforeValue.equals(afterValue)))
same = true;
}
if (!same)
{
if (exists)
{
auditMap.put(buildPath(PROPERTIES, FROM, name), beforeValue);
auditMap.put(buildPath(PROPERTIES, TO, name), afterValue);
from.put(key, beforeValue);
to.put(key, afterValue);
}
else
{
auditMap.put(buildPath(PROPERTIES, DELETE, name), beforeValue);
delete.put(key, beforeValue);
}
}
}
// Check for new values. Record individual values and group as a single map.
Set<QName> newKeys = new HashSet<QName>(toProperties.keySet());
newKeys.removeAll(fromProperties.keySet());
for (QName key: newKeys)
{
// Audit QNames with the prefix set.
key = getPrefixedQName(key);
Serializable afterValue = toProperties.get(key);
String name = replaceInvalidPathChars(key.toPrefixString());
auditMap.put(buildPath(PROPERTIES, ADD, name), afterValue);
add.put(key, afterValue);
}
// Record maps of additions, deletes and paired from and to values.
if (!subAction)
{
if (!add.isEmpty())
{
auditMap.put(buildPath(PROPERTIES, ADD), add);
}
if (!delete.isEmpty())
{
auditMap.put(buildPath(PROPERTIES, DELETE), delete);
}
if (!from.isEmpty())
{
auditMap.put(buildPath(PROPERTIES, FROM), from);
}
if (!to.isEmpty())
{
auditMap.put(buildPath(PROPERTIES, TO), to);
}
}
}
private void addAspectChangesToAuditMap(Map<String, Serializable> auditMap,
String addOrDelete, HashSet<QName> aspects, boolean subAction)
{
// Audit Set<QName> where the QName has the prefix set.
HashSet<QName> prefixedAspects = new HashSet<QName>(aspects.size());
for (QName aspect: aspects)
{
// Audit QNames with the prefix set.
aspect = getPrefixedQName(aspect);
prefixedAspects.add(aspect);
String name = replaceInvalidPathChars(aspect.toPrefixString());
auditMap.put(buildPath(ASPECTS, addOrDelete, name), null);
}
if (!subAction)
{
auditMap.put(buildPath(ASPECTS, addOrDelete), prefixedAspects);
}
}
private void addVersionPropertiesToAuditMap(Map<String, Serializable> auditMap,
HashMap<String, Serializable> properties, boolean subAction)
{
for (Map.Entry<String, Serializable> entry: properties.entrySet())
{
// The key may be the string version ({URI}localName) of a QName.
// If so get the prefixed version.
String key = entry.getKey();
if (key.indexOf(QName.NAMESPACE_PREFIX) == 0)
{
try
{
QName qNameKey = QName.createQName(key);
qNameKey = getPrefixedQName(qNameKey);
key = qNameKey.toPrefixString();
}
catch (InvalidQNameException e)
{
// Its just a String.
}
}
String name = replaceInvalidPathChars(key);
auditMap.put(buildPath(VERSION_PROPERTIES, name), entry.getValue());
}
if (!subAction)
{
auditMap.put(VERSION_PROPERTIES, properties);
}
}
private int getSubAuditDataSize()
{
int size = 0;
// No point doing sub actions if only one!
if (subActionAuditMaps != null && subActionAuditMaps.size() > 1)
{
for (Map<String, Serializable> subActionAuditMap: subActionAuditMaps)
{
size += subActionAuditMap.size();
}
}
return size;
}
private void addSubActionAuditMapsToAuditMap(Map<String, Serializable> auditMap)
{
// No point doing sub actions if only one!
if (subActionAuditMaps != null && subActionAuditMaps.size() > 1)
{
String format = "%0"+Integer.toString(auditMap.size()).length()+"d";
int i = 0;
for (Map<String, Serializable> subActionAuditMap : subActionAuditMaps)
{
String seq = String.format(format, i);
for (Map.Entry<String, Serializable> entry : subActionAuditMap.entrySet())
{
auditMap.put(buildPath(SUB_ACTION, seq, entry.getKey()), entry.getValue());
}
i++;
}
}
}
/**
* Returns a path to be used in an audit map. Unlike {@link AuditApplication#buildPath(String...)}
* the returned value is relative (no leading slash).
* @param components String.. components of the path.
* @return a component path of the supplied values.
*/
private static String buildPath(String... components)
{
StringBuilder sb = new StringBuilder();
for (String component: components)
{
if (sb.length() > 0)
{
sb.append(AUDIT_PATH_SEPARATOR);
}
sb.append(component);
}
return sb.toString();
}
/**
* @return a new QName that has the prefix set or the original if it is unknown.
*/
private QName getPrefixedQName(QName qName)
{
try
{
qName = qName.getPrefixedQName(namespaceService);
}
catch (NamespaceException e)
{
// ignore - go with what we have
}
return qName;
}
/**
* @return a String where all invalid audit path characters are replaced by a '-'.
*/
private String replaceInvalidPathChars(String path)
{
return AuditApplication.AUDIT_INVALID_PATH_COMP_CHAR_PATTERN.matcher(path).replaceAll(INVALID_PATH_CHAR_REPLACEMENT);
}
}