/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* 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/>.
* #L%
*/
package org.alfresco.module.org_alfresco_module_rm.disposition;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry;
import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction;
import org.alfresco.module.org_alfresco_module_rm.event.EventCompletionDetails;
import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Disposition action implementation.
*
* @author Roy Wetherall
* @since 1.0
*/
public class DispositionActionImpl implements DispositionAction,
RecordsManagementModel
{
/** logger */
private static Log logger = LogFactory.getLog(DispositionActionImpl.class);
/** records management service registry */
private RecordsManagementServiceRegistry services;
/** disposition node reference */
private NodeRef dispositionNodeRef;
/** disposition action definition */
private DispositionActionDefinition dispositionActionDefinition;
/**
* Constructor
*
* @param services records management service registry
* @param dispositionActionNodeRef disposition action node reference
*/
public DispositionActionImpl(RecordsManagementServiceRegistry services, NodeRef dispositionActionNodeRef)
{
this.services = services;
this.dispositionNodeRef = dispositionActionNodeRef;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getDispositionActionDefinition()
*/
public DispositionActionDefinition getDispositionActionDefinition()
{
if (dispositionActionDefinition == null)
{
// Get the current action
String id = (String)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_ID);
// Get the disposition instructions for the owning node
NodeRef recordNodeRef = services.getNodeService().getPrimaryParent(this.dispositionNodeRef).getParentRef();
if (recordNodeRef != null)
{
DispositionSchedule ds = services.getDispositionService().getDispositionSchedule(recordNodeRef);
if (ds != null)
{
// Get the disposition action definition
dispositionActionDefinition = ds.getDispositionActionDefinition(id);
}
}
}
return dispositionActionDefinition;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getNodeRef()
*/
public NodeRef getNodeRef()
{
return this.dispositionNodeRef;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getLabel()
*/
public String getLabel()
{
String name = getName();
String label = name;
// get the disposition action from the RM action service
RecordsManagementAction action = services.getRecordsManagementActionService().getDispositionAction(name);
if (action != null)
{
label = action.getLabel();
}
return label;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getId()
*/
public String getId()
{
return (String)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_ID);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getName()
*/
public String getName()
{
return (String)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getAsOfDate()
*/
public Date getAsOfDate()
{
return (Date)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_AS_OF);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#isEventsEligible()
*/
public boolean isEventsEligible()
{
return ((Boolean)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_EVENTS_ELIGIBLE)).booleanValue();
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getCompletedAt()
*/
public Date getCompletedAt()
{
return (Date)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_COMPLETED_AT);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getCompletedBy()
*/
public String getCompletedBy()
{
return (String)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_COMPLETED_BY);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.DispositionAction#getStartedAt()
*/
public Date getStartedAt()
{
return (Date)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_STARTED_AT);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.DispositionAction#getStartedBy()
*/
public String getStartedBy()
{
return (String)services.getNodeService().getProperty(this.dispositionNodeRef, PROP_DISPOSITION_ACTION_STARTED_BY);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#getEventCompletionDetails()
*/
public List<EventCompletionDetails> getEventCompletionDetails()
{
List<ChildAssociationRef> assocs = services.getNodeService().getChildAssocs(
this.dispositionNodeRef,
ASSOC_EVENT_EXECUTIONS,
RegexQNamePattern.MATCH_ALL);
List<EventCompletionDetails> result = new ArrayList<EventCompletionDetails>(assocs.size());
for (ChildAssociationRef assoc : assocs)
{
result.add(getEventCompletionDetailsFromNodeRef(assoc.getChildRef()));
}
return result;
}
/**
* Helper method to create object representation of event completed details from
* node reference.
*
* @param nodeRef node reference
* @return {@link EventCompletionDetails} event completion details
*/
private EventCompletionDetails getEventCompletionDetailsFromNodeRef(NodeRef nodeRef)
{
// get the properties
Map<QName, Serializable> props = this.services.getNodeService().getProperties(nodeRef);
// get the event name
String eventName = (String)props.get(PROP_EVENT_EXECUTION_NAME);
// create event completion details
EventCompletionDetails ecd = new EventCompletionDetails(
nodeRef,
eventName,
services.getRecordsManagementEventService().getEvent(eventName).getDisplayLabel(),
getBooleanValue(props.get(PROP_EVENT_EXECUTION_AUTOMATIC), false),
getBooleanValue(props.get(PROP_EVENT_EXECUTION_COMPLETE), false),
(Date)props.get(PROP_EVENT_EXECUTION_COMPLETED_AT),
(String)props.get(PROP_EVENT_EXECUTION_COMPLETED_BY));
return ecd;
}
/**
* Helper method to deal with boolean values
*
* @param value
* @param defaultValue
* @return
*/
private boolean getBooleanValue(Object value, boolean defaultValue)
{
boolean result = defaultValue;
if (value instanceof Boolean)
{
result = ((Boolean)value).booleanValue();
}
return result;
}
/**
* Gets the event completion details for the named event.
* <p>
* Returns null if event can not be found.
*
* @param eventName name of the event
* @return {@link EventCompletionDetails} event completion details for named event, null otherwise
*
* @since 2.2
*/
@Override
public EventCompletionDetails getEventCompletionDetails(String eventName)
{
EventCompletionDetails result = null;
List<ChildAssociationRef> assocs = services.getNodeService().getChildAssocsByPropertyValue(dispositionNodeRef, PROP_EVENT_EXECUTION_NAME, eventName);
if (!assocs.isEmpty())
{
if (assocs.size() != 1)
{
throw new AlfrescoRuntimeException("Unable to get event completion details, because more than one child was found for event " + eventName);
}
result = getEventCompletionDetailsFromNodeRef(assocs.get(0).getChildRef());
}
return result;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#completeEvent(java.lang.String, java.util.Date, java.lang.String)
*/
@Override
public void completeEvent(final String eventName, final Date completedAt, final String completedBy)
{
final EventCompletionDetails event = getEventCompletionDetails(eventName);
if (event != null && !event.isEventComplete())
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// use "now" if no completed date set
Date completedAtValue = completedAt;
if (completedAt == null)
{
completedAtValue = new Date();
}
// use the currently authenticated user if none set
String completedByValue = completedBy;
if (completedBy == null)
{
completedByValue = AuthenticationUtil.getFullyAuthenticatedUser();
}
// Update the event so that it is complete
NodeRef eventNodeRef = event.getNodeRef();
Map<QName, Serializable> props = services.getNodeService().getProperties(eventNodeRef);
props.put(PROP_EVENT_EXECUTION_COMPLETE, true);
props.put(PROP_EVENT_EXECUTION_COMPLETED_AT, completedAtValue);
props.put(PROP_EVENT_EXECUTION_COMPLETED_BY, completedByValue);
services.getNodeService().setProperties(eventNodeRef, props);
// Check to see if the events eligible property needs to be updated
updateEventEligible();
return null;
}
});
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#undoEvent(java.lang.String)
*/
@Override
public void undoEvent(final String eventName)
{
final EventCompletionDetails event = getEventCompletionDetails(eventName);
if (event != null && event.isEventComplete())
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// Update the event so that it is undone
NodeRef eventNodeRef = event.getNodeRef();
Map<QName, Serializable> props = services.getNodeService().getProperties(eventNodeRef);
props.put(PROP_EVENT_EXECUTION_COMPLETE, false);
props.put(PROP_EVENT_EXECUTION_COMPLETED_AT, null);
props.put(PROP_EVENT_EXECUTION_COMPLETED_BY, null);
services.getNodeService().setProperties(eventNodeRef, props);
// Check to see if the events eligible property needs to be updated
updateEventEligible();
return null;
}
});
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#refreshEvents()
*/
@SuppressWarnings("unchecked")
@Override
public void refreshEvents()
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// go through the current events on the next action and remove any that are not present any more
List<String> stepEvents = (List<String>) services.getNodeService().getProperty(getDispositionActionDefinition().getNodeRef(), PROP_DISPOSITION_EVENT);
List<EventCompletionDetails> eventsList = getEventCompletionDetails();
List<String> nextActionEvents = new ArrayList<String>(eventsList.size());
for (EventCompletionDetails event : eventsList)
{
// take note of the event names present on the next action
String eventName = event.getEventName();
nextActionEvents.add(eventName);
// if the event has been removed delete from next action
if (stepEvents != null && !stepEvents.contains(event.getEventName()))
{
// remove the child association representing the event
services.getNodeService().removeChild(getNodeRef(), event.getNodeRef());
if (logger.isDebugEnabled())
{
logger.debug("Removed '" + eventName + "' from next action '" + getName() +
"' (" + getNodeRef() + ")");
}
}
}
// go through the disposition action definition step events and add any new ones
if (stepEvents != null)
{
for (String eventName : stepEvents)
{
if (!nextActionEvents.contains(eventName))
{
// add the details of the new event
addEventCompletionDetails(services.getRecordsManagementEventService().getEvent(eventName));
if (logger.isDebugEnabled())
{
logger.debug("Added '" + eventName + "' to next action '" + getName() +
"' (" + getNodeRef() + ")");
}
}
}
}
// NOTE: eventsList contains all the events that have been updated!
// TODO: manually update the search properties for the parent node!
// finally since events may have changed re-calculate the events eligible flag
boolean eligible = updateEventEligible();
if (logger.isDebugEnabled())
{
logger.debug("Set events eligible flag to '" + eligible + "' for next action '" + getName() +
"' (" + getNodeRef() + ")");
}
return null;
}
});
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction#addEventCompletionDetails(org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent)
*/
@Override
public void addEventCompletionDetails(RecordsManagementEvent event)
{
Map<QName, Serializable> eventProps = new HashMap<QName, Serializable>(7);
eventProps.put(PROP_EVENT_EXECUTION_NAME, event.getName());
// TODO display label
eventProps.put(PROP_EVENT_EXECUTION_AUTOMATIC, event.getRecordsManagementEventType().isAutomaticEvent());
eventProps.put(PROP_EVENT_EXECUTION_COMPLETE, false);
// Create the event execution object
services.getNodeService().createNode(getNodeRef(),
ASSOC_EVENT_EXECUTIONS,
ASSOC_EVENT_EXECUTIONS,
TYPE_EVENT_EXECUTION,
eventProps);
}
/**
* Calculates and updates the <code>rma:dispositionEventsEligible</code>
* property for the given next disposition action.
*
* @param nextAction The next disposition action
* @return The result of calculation
*
* @since 2.2
*/
private boolean updateEventEligible()
{
boolean eligible = false;
// get the events for the next disposition action
List<EventCompletionDetails> events = getEventCompletionDetails();
if (!events.isEmpty())
{
// get the disposition action definition
DispositionActionDefinition dispositionActionDefinition = getDispositionActionDefinition();
if (dispositionActionDefinition != null)
{
if (!dispositionActionDefinition.eligibleOnFirstCompleteEvent())
{
// if one event is complete then the disposition action is eligible
eligible = true;
for (EventCompletionDetails event : events)
{
if (!event.isEventComplete())
{
eligible = false;
break;
}
}
}
else
{
// all events must be complete for the disposition action to be eligible
for (EventCompletionDetails event : events)
{
if (event.isEventComplete())
{
eligible = true;
break;
}
}
}
}
}
// Update the property with the eligible value
services.getNodeService().setProperty(getNodeRef(), PROP_DISPOSITION_EVENTS_ELIGIBLE, eligible);
return eligible;
}
}