Merge remote-tracking branch 'remotes/origin/master' into xperimental/RM-6313UpgradeRMSchedulers_remote

This commit is contained in:
Rodica Sutu
2018-05-29 11:56:21 +03:00
29 changed files with 850 additions and 37 deletions

View File

@@ -662,7 +662,7 @@ public abstract class BaseAPI
RETENTION_GHOST, RETENTION_GHOST,
RETENTION_ELIGIBLE_FIRST_EVENT, RETENTION_ELIGIBLE_FIRST_EVENT,
RETENTION_EVENTS, RETENTION_EVENTS,
COMBINE_DISPOSITION_STEP_CONDITIONS
} }
/** /**
@@ -674,6 +674,8 @@ public abstract class BaseAPI
CUT_OFF("cutoff"), CUT_OFF("cutoff"),
UNDO_CUT_OFF("undoCutoff"), UNDO_CUT_OFF("undoCutoff"),
TRANSFER("transfer"), TRANSFER("transfer"),
COMPLETE_EVENT("completeEvent"),
UNDO_EVENT("undoEvent"),
DESTROY("destroy"); DESTROY("destroy");
String action; String action;

View File

@@ -0,0 +1,49 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2018 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.rest.core.v0;
public enum RMEvents
{
ABOLISHED("abolished"),
ALL_ALLOWANCES_GRANTED_ARE_TERMINATED("all_allowances_granted_are_terminated"),
CASE_CLOSED("case_closed"),
DECLASSIFICATION_REVIEW("declassification_review"),
OBSOLETE("obsolete"),
NO_LONGER_NEEDED("no_longer_needed"),
STUDY_COMPLETE("study_complete");
private String eventName;
RMEvents(String eventName)
{
this.eventName = eventName;
}
public String getEventName()
{
return eventName;
}
}

View File

@@ -58,6 +58,8 @@ public class FilePlanComponentFields
public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_ACTION_NAME = "rma:recordSearchDispositionActionName"; public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_ACTION_NAME = "rma:recordSearchDispositionActionName";
public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS_ELIGIBLE = "rma:recordSearchDispositionEventsEligible"; public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS_ELIGIBLE = "rma:recordSearchDispositionEventsEligible";
public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS = "rma:recordSearchDispositionInstructions"; public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_INSTRUCTIONS = "rma:recordSearchDispositionInstructions";
public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_BY = "rma:declassificationReviewCompletedBy";
public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_AT = "rma:declassificationReviewCompletedAt";
/** File plan properties */ /** File plan properties */

View File

@@ -35,6 +35,7 @@ import static org.testng.AssertJUnit.fail;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Arrays; import java.util.Arrays;
@@ -46,6 +47,7 @@ import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.dataprep.ContentService; import org.alfresco.dataprep.ContentService;
import org.alfresco.dataprep.UserService; import org.alfresco.dataprep.UserService;
import org.alfresco.rest.core.v0.BaseAPI; import org.alfresco.rest.core.v0.BaseAPI;
import org.alfresco.rest.core.v0.RMEvents;
import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@@ -325,9 +327,9 @@ public class RMRolesAndActionsAPI extends BaseAPI
/** /**
* Perform an action on the record folder * Perform an action on the record folder
* *
* @param user the user closing the folder * @param user the user executing the action
* @param password the user's password * @param password the user's password
* @param contentName the record folder name * @param contentName the content name
* @param date the date to be updated * @param date the date to be updated
* @return The HTTP response. * @return The HTTP response.
*/ */
@@ -349,6 +351,56 @@ public class RMRolesAndActionsAPI extends BaseAPI
return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API); return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API);
} }
/**
* Complete an event on the record/record folder
*
* @param user the user executing the action
* @param password the user's password
* @param nodeName the node name
* @param event the event to be completed
* @param date the date to be updated
* @return The HTTP response.
*/
public HttpResponse completeEvent(String user, String password, String nodeName, RMEvents event, Instant date)
{
String recNodeRef = getNodeRefSpacesStore() + contentService.getNodeRef(user, password, RM_SITE_ID, nodeName);
JSONObject requestParams = new JSONObject();
requestParams.put("name", RM_ACTIONS.COMPLETE_EVENT.getAction());
requestParams.put("nodeRef", recNodeRef);
date = (date != null) ? date : Instant.now();
String formattedDate = DateTimeFormatter.ISO_INSTANT.format(date);
requestParams.put("params", new JSONObject()
.put("eventName", event.getEventName())
.put("eventCompletedBy", user)
.put("eventCompletedAt", new JSONObject()
.put("iso8601", formattedDate)
)
);
return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API);
}
/**
* Undo an event on the record/record folder
*
* @param user the user executing the action
* @param password the user's password
* @param contentName the content name
* @param event the event to be undone
* @return The HTTP response.
*/
public HttpResponse undoEvent(String user, String password, String contentName, RMEvents event)
{
String recNodeRef = getNodeRefSpacesStore() + contentService.getNodeRef(user, password, RM_SITE_ID, contentName);
JSONObject requestParams = new JSONObject();
requestParams.put("name", RM_ACTIONS.UNDO_EVENT.getAction());
requestParams.put("nodeRef", recNodeRef);
requestParams.put("params", new JSONObject()
.put("eventName", event.getEventName()));
return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API);
}
/** /**
* Deletes every item in the given container * Deletes every item in the given container
* *

View File

@@ -51,6 +51,8 @@ public class RecordCategoriesAPI extends BaseAPI
private static final Logger LOGGER = LoggerFactory.getLogger(RecordCategoriesAPI.class); private static final Logger LOGGER = LoggerFactory.getLogger(RecordCategoriesAPI.class);
private static final String RM_ACTIONS_API = "{0}rma/actions/ExecutionQueue"; private static final String RM_ACTIONS_API = "{0}rma/actions/ExecutionQueue";
private static final String DISPOSITION_ACTIONS_API = "{0}node/{1}/dispositionschedule/dispositionactiondefinitions"; private static final String DISPOSITION_ACTIONS_API = "{0}node/{1}/dispositionschedule/dispositionactiondefinitions";
private static final String DISPOSITION_SCHEDULE_API = "{0}node/{1}/dispositionschedule";
/** /**
* Creates a retention schedule for the category given as parameter * Creates a retention schedule for the category given as parameter
@@ -71,6 +73,21 @@ public class RecordCategoriesAPI extends BaseAPI
return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API); return doPostJsonRequest(user, password, SC_OK, requestParams, RM_ACTIONS_API);
} }
/**
* Get the disposition schedule nodeRef
*
* @param user
* @param password
* @param categoryName
* @return the disposition schedule nodeRef
*/
public String getDispositionScheduleNodeRef(String user, String password, String categoryName)
{
String catNodeRef = NODE_PREFIX + getItemNodeRef(user, password, "/" + categoryName);
JSONObject dispositionSchedule = doGetRequest(user, password, MessageFormat.format(DISPOSITION_SCHEDULE_API, "{0}", catNodeRef));
return dispositionSchedule.getJSONObject("data").getString("nodeRef").replace(getNodeRefSpacesStore(), "");
}
/** /**
* Sets retention schedule authority and instructions, also if it is applied to records or folders * Sets retention schedule authority and instructions, also if it is applied to records or folders
* *
@@ -108,7 +125,12 @@ public class RecordCategoriesAPI extends BaseAPI
addPropertyToRequest(requestParams, "period", properties, RETENTION_SCHEDULE.RETENTION_PERIOD); addPropertyToRequest(requestParams, "period", properties, RETENTION_SCHEDULE.RETENTION_PERIOD);
addPropertyToRequest(requestParams, "ghostOnDestroy", properties, RETENTION_SCHEDULE.RETENTION_GHOST); addPropertyToRequest(requestParams, "ghostOnDestroy", properties, RETENTION_SCHEDULE.RETENTION_GHOST);
addPropertyToRequest(requestParams, "periodProperty", properties, RETENTION_SCHEDULE.RETENTION_PERIOD_PROPERTY); addPropertyToRequest(requestParams, "periodProperty", properties, RETENTION_SCHEDULE.RETENTION_PERIOD_PROPERTY);
addPropertyToRequest(requestParams, "events", properties, RETENTION_SCHEDULE.RETENTION_EVENTS); String events = getPropertyValue(properties, RETENTION_SCHEDULE.RETENTION_EVENTS);
if(!events.equals(""))
{
requestParams.append("events", events);
}
addPropertyToRequest(requestParams, "combineDispositionStepConditions", properties, RETENTION_SCHEDULE.COMBINE_DISPOSITION_STEP_CONDITIONS);
addPropertyToRequest(requestParams, "eligibleOnFirstCompleteEvent", properties, RETENTION_SCHEDULE.RETENTION_ELIGIBLE_FIRST_EVENT); addPropertyToRequest(requestParams, "eligibleOnFirstCompleteEvent", properties, RETENTION_SCHEDULE.RETENTION_ELIGIBLE_FIRST_EVENT);
return doPostJsonRequest(user, password, SC_OK, requestParams, MessageFormat.format(DISPOSITION_ACTIONS_API, "{0}", catNodeRef)); return doPostJsonRequest(user, password, SC_OK, requestParams, MessageFormat.format(DISPOSITION_ACTIONS_API, "{0}", catNodeRef));

View File

@@ -64,8 +64,8 @@ public class SearchAPI extends BaseAPI
private static final String RM_SEARCH_ENDPOINT = "{0}alfresco/s/slingshot/rmsearch/{1}?{2}"; private static final String RM_SEARCH_ENDPOINT = "{0}alfresco/s/slingshot/rmsearch/{1}?{2}";
/** RM document search filters */ /** RM document search filters */
private static final String RM_DEFAULT_RECORD_FILTERS = private static final String RM_DEFAULT_NODES_FILTERS =
"records/true,undeclared/true,vital/false,folders/false,categories/false,frozen/false,cutoff/false"; "records/true,undeclared/true,vital/false,folders/{0},categories/{1},frozen/false,cutoff/false";
/** /**
* Perform search request on search endpoint as a user. * Perform search request on search endpoint as a user.
@@ -91,6 +91,7 @@ public class SearchAPI extends BaseAPI
* @param site * @param site
* @param query * @param query
* @param filters * @param filters
* @param sortby
* @return search results (see API reference for more details), null for any errors * @return search results (see API reference for more details), null for any errors
*/ */
public JSONObject rmSearch( public JSONObject rmSearch(
@@ -98,11 +99,16 @@ public class SearchAPI extends BaseAPI
String password, String password,
String site, String site,
String query, String query,
String filters) String filters,
String sortby)
{ {
List<BasicNameValuePair> searchParameters = new ArrayList<BasicNameValuePair>(); List<BasicNameValuePair> searchParameters = new ArrayList<BasicNameValuePair>();
searchParameters.add(new BasicNameValuePair("query", query)); searchParameters.add(new BasicNameValuePair("query", query));
searchParameters.add(new BasicNameValuePair("filters", filters)); searchParameters.add(new BasicNameValuePair("filters", filters));
if (sortby != null)
{
searchParameters.add(new BasicNameValuePair("sortby", sortby));
}
String requestURL = MessageFormat.format( String requestURL = MessageFormat.format(
RM_SEARCH_ENDPOINT, RM_SEARCH_ENDPOINT,
@@ -114,20 +120,23 @@ public class SearchAPI extends BaseAPI
} }
/** /**
* Search as a user for records on site "rm" matching query, using SearchAPI.RM_DEFAULT_RECORD_FILTERS * Search as a user for nodes on site "rm" matching query, using SearchAPI.RM_DEFAULT_RECORD_FILTERS and sorted
* by sortby
* <br> * <br>
* If more fine-grained control of search parameters is required, use rmSearch() directly. * If more fine-grained control of search parameters is required, use rmSearch() directly.
* @param username * @param username
* @param password * @param password
* @param query * @param query
* @param sortby
* @return list of record names * @return list of record names
*/ */
public List<String> searchForRecordsAsUser( public List<String> searchForRecordsAsUser(
String username, String username, String password,
String password, String query, String sortby,
String query) boolean includeCategories, boolean includeFolders)
{ {
return getItemNames(rmSearch(username, password, "rm", query, RM_DEFAULT_RECORD_FILTERS)); String searchFilterParamaters = MessageFormat.format(RM_DEFAULT_NODES_FILTERS, Boolean.toString(includeFolders), Boolean.toString(includeCategories));
return getItemNames(rmSearch(username, password, "rm", query, searchFilterParamaters, sortby));
} }
/** /**

View File

@@ -0,0 +1,139 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2018 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.rest.v0.service;
import java.util.HashMap;
import org.alfresco.rest.core.v0.BaseAPI;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
import org.alfresco.rest.v0.RecordCategoriesAPI;
import org.alfresco.utility.data.DataUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Service for different disposition schedule actions
*
* @author jcule
* @since 2.7.0.1
*/
@Service
public class DispositionScheduleService extends BaseAPI
{
@Autowired
private RecordCategoriesAPI recordCategoriesAPI;
@Autowired
private DataUser dataUser;
/**
* Helper method for adding a cut off after period step
*
* @param categoryName the category in whose schedule the step will be added
* @param period
* @return
*/
public void addCutOffAfterPeriodStep(String categoryName, String period)
{
HashMap<RETENTION_SCHEDULE, String> cutOffStep = new HashMap<>();
cutOffStep.put(RETENTION_SCHEDULE.NAME, "cutoff");
cutOffStep.put(RETENTION_SCHEDULE.RETENTION_PERIOD, period);
cutOffStep.put(RETENTION_SCHEDULE.DESCRIPTION, "Cut off after a period step");
recordCategoriesAPI.addDispositionScheduleSteps(dataUser.getAdminUser().getUsername(),
dataUser.getAdminUser().getPassword(), categoryName, cutOffStep);
}
/**
* Helper method for adding a cut off after an event occurs step
*
* @param categoryName the category in whose schedule the step will be added
* @param events
*/
public void addCutOffAfterEventStep(String categoryName, String events)
{
HashMap<RETENTION_SCHEDULE, String> cutOffStep = new HashMap<>();
cutOffStep.put(RETENTION_SCHEDULE.NAME, "cutoff");
cutOffStep.put(RETENTION_SCHEDULE.RETENTION_EVENTS, events);
cutOffStep.put(RETENTION_SCHEDULE.DESCRIPTION, "Cut off after event step");
recordCategoriesAPI.addDispositionScheduleSteps(dataUser.getAdminUser().getUsername(),
dataUser.getAdminUser().getPassword(), categoryName, cutOffStep);
}
/**
* Helper method for adding an accession step
*
* @param timeOrEvent
* @param events
* @param period
* @param periodProperty
* @param combineConditions
* @return
*/
public void addAccessionStep(String categoryName, Boolean timeOrEvent, String events, String period, String
periodProperty, Boolean combineConditions)
{
HashMap<RETENTION_SCHEDULE, String> accessionStep = new HashMap<>();
accessionStep.put(RETENTION_SCHEDULE.NAME, "accession");
accessionStep.put(RETENTION_SCHEDULE.COMBINE_DISPOSITION_STEP_CONDITIONS, Boolean.toString(combineConditions));
accessionStep.put(RETENTION_SCHEDULE.RETENTION_PERIOD, period);
accessionStep.put(RETENTION_SCHEDULE.RETENTION_PERIOD_PROPERTY, periodProperty);
if (!timeOrEvent)
{
accessionStep.put(RETENTION_SCHEDULE.RETENTION_ELIGIBLE_FIRST_EVENT, Boolean.toString(timeOrEvent));
}
accessionStep.put(RETENTION_SCHEDULE.RETENTION_EVENTS, events);
accessionStep.put(RETENTION_SCHEDULE.DESCRIPTION,
"Accession step with time and event conditions.");
recordCategoriesAPI.addDispositionScheduleSteps(dataUser.getAdminUser().getUsername(),
dataUser.getAdminUser().getPassword(), categoryName, accessionStep);
}
/**
* Helper method to create retention schedule with general fields for the given category as admin
* and apply it to the records
*
* @param categoryName
* @param appliedToRecords
*/
public void createCategoryRetentionSchedule(String categoryName, Boolean appliedToRecords)
{
recordCategoriesAPI.createRetentionSchedule(dataUser.getAdminUser().getUsername(),
dataUser.getAdminUser().getPassword(), categoryName);
String retentionScheduleNodeRef = recordCategoriesAPI.getDispositionScheduleNodeRef(
dataUser.getAdminUser().getUsername(), dataUser.getAdminUser().getPassword(), categoryName);
HashMap<RETENTION_SCHEDULE, String> retentionScheduleGeneralFields = new HashMap<>();
retentionScheduleGeneralFields.put(RETENTION_SCHEDULE.RETENTION_AUTHORITY, "Authority");
retentionScheduleGeneralFields.put(RETENTION_SCHEDULE.RETENTION_INSTRUCTIONS, "Instructions");
recordCategoriesAPI.setRetentionScheduleGeneralFields(dataUser.getAdminUser().getUsername(),
dataUser.getAdminUser().getPassword(), retentionScheduleNodeRef, retentionScheduleGeneralFields,
appliedToRecords);
}
}

View File

@@ -658,7 +658,8 @@ public class BaseRMRestTest extends RestTest
names.add(childNode.onModel().getName()); names.add(childNode.onModel().getName());
}); });
break; break;
} else }
else
{ {
counter++; counter++;
} }
@@ -673,10 +674,12 @@ public class BaseRMRestTest extends RestTest
* *
* @param user * @param user
* @param term * @param term
* @param sortby
* @param expectedResults
* @return * @return
* @throws Exception
*/ */
public List<String> searchForRMContentAsUser(UserModel user, String term, String expectedResult) throws Exception public List<String> searchForRMContentAsUser(UserModel user, String term, String sortby, boolean includeFolders,
boolean includeCategories, List<String> expectedResults)
{ {
List<String> results = new ArrayList<>(); List<String> results = new ArrayList<>();
// wait for solr indexing // wait for solr indexing
@@ -684,26 +687,28 @@ public class BaseRMRestTest extends RestTest
int waitInMilliSeconds = 6000; int waitInMilliSeconds = 6000;
while (counter < 3) while (counter < 3)
{ {
results = searchApi.searchForRecordsAsUser(user.getUsername(), user.getPassword(), term);
if ((results != null && !results.isEmpty() && results.contains(expectedResult)))
{
break;
} else
{
counter++;
}
// double wait time to not overdo solr search
waitInMilliSeconds = (waitInMilliSeconds * 2);
synchronized (this) synchronized (this)
{ {
try try
{ {
this.wait(waitInMilliSeconds); this.wait(waitInMilliSeconds);
} catch (InterruptedException e) }
catch (InterruptedException e)
{ {
} }
} }
results = searchApi.searchForRecordsAsUser(user.getUsername(), user.getPassword(), term, sortby,
includeFolders, includeCategories);
if (!results.isEmpty() && results.containsAll(expectedResults))
{
break;
}
else
{
counter++;
}
// double wait time to not overdo solr search
waitInMilliSeconds = (waitInMilliSeconds * 2);
} }
return results; return results;
} }

View File

@@ -11,14 +11,14 @@
* Easy Access Records * Easy Access Records
* Physical Records * Physical Records
* Record Import and Export * Record Import and Export
* Version Records * [Version Records](./versionRecords)
* Retention * Retention
* [Destruction](./destruction) * [Destruction](./destruction)
* Retention Schedules and Events * Retention Schedules and Events
* Transfer and Accession * Transfer and Accession
* Security * Security
* [Extended permission service](extendedPermissionService.md) * [Extended permission service](security/extendedPermissionService.md)
* Roles, Capabilities and Permissions * [Roles and Capabilities](security/rolesAndCapabilities.md)
* Discovery * Discovery
* Governance Search * Governance Search
* Legal Holds * Legal Holds
@@ -27,3 +27,5 @@
* Governance Rules * Governance Rules
* Core Module Services * Core Module Services
* [RM Patch Service](./PatchService.md) * [RM Patch Service](./PatchService.md)
* Build and Release
* [Build](./build)

View File

@@ -0,0 +1,6 @@
## GS Build ![](https://img.shields.io/badge/Document_Level-In_Progress-yellow.svg?style=flat-square)
Build location: https://bamboo.alfresco.com/bamboo/browse/RM (not externally accessible.)
Build Flow:
![build](./resource/build.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,66 @@
@startuml
Title: Governance Services Build Pipeline (RM HEAD)
'build plans:
'Ent UI: Automated UI Tests Enterprise
'Com API: Automation Community REST API
'Ent API: Automation Enterprise REST API
'Community
'Com UI: Community Automated UI Tests
'Enterprise
'Ent L1: Enterprise Level 1 Automated UI Tests
'Ent L2: Level 2 Automated UI Tests Enterprise
'RM Benchmark Driver
start
if(Trigger) then (commit to path)
if (rm-community/*)
:Community;
fork
:Ent L1;
fork again
:Enterprise;
fork
:Ent L2;
fork again
:Ent UI;
end fork
end fork
elseif (rm-enterprise/*)
:Enterprise;
fork
:Ent L2;
fork again
:Ent UI;
end fork
elseif (rm-automation/*)
fork
:Ent L1;
fork again
:Ent L2;
fork again
:Ent UI;
end fork
stop
elseif (rm-community-rest-api/*)
:Com API;
stop
elseif (rm-enterprise-rest-api/*)
:Ent API;
stop
elseif (rm-benchmark-driver/*)
:Benchmark;
stop
else
end
endif
else (Time: 1am)
:Community UI;
stop
endif
:Release Step;
end
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,41 @@
## Alfresco Governance Services' Roles and Capabilities
![Completeness Badge](https://img.shields.io/badge/Document_Level-InProgress-yellow.svg?style=flat-square)
![Version Badge](https://img.shields.io/badge/Version-Current-blue.svg?style=flat-square)
### Purpose
Roles and capabilities allow the GS system to provide a finer grain security evaluation, determining whether an authority has the capability to perform a perticular action on a node.
### Overview
Roles are defined as a collection of capabilities. A capability, generally, has a one to one relationship with an action within the system.
Authorities are assigned roles. If an authority is assigned to a role then it that authority has the capabilities contained within that role, allowing them to perform the related actions.
An authority can be assigned many roles, with the associated capabilities being additive.
Capabilties are evaluated in addition to any ACLs attached to a node, but they are mutally exclusive. A authority may have the capability, but not the permissions on a node and vice versa.
### Design
Roles are implementented as groups. So for every role that is created, there is a corresponding group within the system.
Capabilities are implemented as permissions. In order add a new capability to the system, the extended RM permission model needs to be extended.
When a capability is added to a role, then the capability group is assigned the capability role on the root file plan node.
In this way the permissions of the systems roles reflect their capabilities on the file plan via the capability permissions assigned.
When an authority is assigned to a role, that authority is added as a member of the corresponding role group. In this way they inherit the capability permissions on the file plan that relate to that role group.
If a user attempts to perform an action on a records management artifact which has a related capability. Assuming the user has permission to see the artifact in the first place, then the users capability to perform the action is evaluated.
This is done by firstly determining whether the capability is relevant for this 'kind' of records management artifact. For example the addHold capability is not relevant for a record category.
Then the capability permission is evaluated by traversing to the file plan node and checking whether the current user has the capabilty permission byt virtue of it's membership of the right role group.
Finally any further conditions attached to the capability are evaluated.
![](../resource/image/CapabilitiesAndRoles.png)

View File

@@ -0,0 +1,20 @@
## Version Records ![](https://img.shields.io/badge/Document_Level-In_Progress-yellow.svg?style=flat-square)
### Notes:
NodesService varies depending on store. Version Service has a different service that hydrates effectively fake nodes (which contain url, version details, associations, aspects, as denormalised meta data) back into a full node
Recorded Versions take content out of version store and create a record by version store implementation extension.
Declaring record as version - standard use case is auto declaring or via records. Head version is extracted to a record, rather than a new version being created
Records are linked by association
Disposition events can be triggered automatically from versioning events.
### Diagram:
![Version Records Primer](./RecordedVersions.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1132,6 +1132,19 @@
<tokenised>false</tokenised> <tokenised>false</tokenised>
</index> </index>
</property> </property>
<property name="rma:declassificationReviewCompletedAt">
<type>d:date</type>
<protected>true</protected>
</property>
<property name="rma:declassificationReviewCompletedBy">
<type>d:text</type>
<protected>true</protected>
<index enabled="true">
<atomic>true</atomic>
<stored>false</stored>
<tokenised>false</tokenised>
</index>
</property>
</properties> </properties>
</aspect> </aspect>

View File

@@ -468,12 +468,18 @@
<property name="permissionService" ref="PermissionService" /> <property name="permissionService" ref="PermissionService" />
<property name="personService" ref="PersonService" /> <property name="personService" ref="PersonService" />
<property name="recordCategoryUtil" ref="RecordCategoryUtil" /> <property name="recordCategoryUtil" ref="RecordCategoryUtil" />
<property name="classificationReasonsUtil" ref="ClassificationReasonsUtil" />
</bean> </bean>
<bean id="RecordCategoryUtil" class="org.alfresco.module.org_alfresco_module_rm.script.slingshot.RecordCategoryUtil"> <bean id="RecordCategoryUtil" class="org.alfresco.module.org_alfresco_module_rm.script.slingshot.RecordCategoryUtil">
<property name="nodeService" ref="NodeService"/> <property name="nodeService" ref="NodeService"/>
</bean> </bean>
<bean id="ClassificationReasonsUtil" class="org.alfresco.module.org_alfresco_module_rm.script.slingshot.ClassificationReasonsUtil">
<property name="nodeService" ref="NodeService" />
</bean>
<bean <bean
id="webscript.org.alfresco.slingshot.rmsearch.rmsearchproperties.get" id="webscript.org.alfresco.slingshot.rmsearch.rmsearchproperties.get"
class="org.alfresco.module.org_alfresco_module_rm.script.slingshot.RMSearchPropertiesGet" class="org.alfresco.module.org_alfresco_module_rm.script.slingshot.RMSearchPropertiesGet"

View File

@@ -35,11 +35,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry; 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.action.RecordsManagementAction;
import org.alfresco.module.org_alfresco_module_rm.event.EventCompletionDetails; 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.event.RecordsManagementEvent;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.script.slingshot.RMSearchGet;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -328,6 +330,12 @@ public class DispositionActionImpl implements DispositionAction,
props.put(PROP_EVENT_EXECUTION_COMPLETED_BY, completedByValue); props.put(PROP_EVENT_EXECUTION_COMPLETED_BY, completedByValue);
services.getNodeService().setProperties(eventNodeRef, props); services.getNodeService().setProperties(eventNodeRef, props);
// check a specific event from rmEventConfigBootstrap.json
if (eventName.equals("declassification_review"))
{
setDeclassificationReview(eventNodeRef, completedAtValue, completedByValue);
}
// Check to see if the events eligible property needs to be updated // Check to see if the events eligible property needs to be updated
updateEventEligible(); updateEventEligible();
@@ -518,4 +526,38 @@ public class DispositionActionImpl implements DispositionAction,
return eligible; return eligible;
} }
/**
* Sets declassification review authority and date on records and record folder
*
* @param eventNodeRef Declassification review event node ref
* @param completedAtValue Declassification review authority
* @param completedByValue Declassification review date
*/
private void setDeclassificationReview(NodeRef eventNodeRef, Date completedAtValue, String completedByValue)
{
NodeRef nextDispositionActionNodeRef = services.getNodeService().getPrimaryParent(eventNodeRef).getParentRef();
NodeRef nodeRef = services.getNodeService().getPrimaryParent(nextDispositionActionNodeRef).getParentRef();
setPropsOnContent(nodeRef, completedAtValue, completedByValue);
// check if the node is a record folder then set the declassification review on the records also
if (services.getNodeService().getType(nodeRef).equals(RecordsManagementModel.TYPE_RECORD_FOLDER))
{
// get all the records inside the record folder
List<ChildAssociationRef> records = services.getNodeService().getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef child : records)
{
NodeRef recordNodeRef = child.getChildRef();
setPropsOnContent(recordNodeRef, completedAtValue, completedByValue);
}
}
}
private void setPropsOnContent(NodeRef nodeRef, Date completedAtValue, String completedByValue)
{
Map<QName, Serializable> nodeProps = services.getNodeService().getProperties(nodeRef);
nodeProps.put(PROP_RS_DECLASSIFICATION_REVIEW_COMPLETED_AT, completedAtValue);
nodeProps.put(PROP_RS_DECLASSIFICATION_REVIEW_COMPLETED_BY, completedByValue);
services.getNodeService().setProperties(nodeRef, nodeProps);
}
} }

View File

@@ -482,8 +482,9 @@ public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JS
{ {
if(!details.isEventComplete()) if(!details.isEventComplete())
{ {
((HashMap) rmNodeValues.get("properties")).put("combineDispositionStepConditions", nodeService.getProperty(dispositionService.getNextDispositionAction(nodeRef).getDispositionActionDefinition().getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS)); HashMap properties = ((HashMap) rmNodeValues.get("properties"));
((HashMap) rmNodeValues.get("properties")).put("incompleteDispositionEvent", details.getEventName()); properties.put("combineDispositionStepConditions", nodeService.getProperty(dispositionService.getNextDispositionAction(nodeRef).getDispositionActionDefinition().getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS));
properties.put("incompleteDispositionEvent", details.getEventName());
break; break;
} }
} }

View File

@@ -240,7 +240,6 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel
QName PROP_RS_DISPOSITION_ACTION_AS_OF = QName.createQName(RM_URI, "recordSearchDispositionActionAsOf"); QName PROP_RS_DISPOSITION_ACTION_AS_OF = QName.createQName(RM_URI, "recordSearchDispositionActionAsOf");
QName PROP_RS_DISPOSITION_EVENTS_ELIGIBLE = QName.createQName(RM_URI, "recordSearchDispositionEventsEligible"); QName PROP_RS_DISPOSITION_EVENTS_ELIGIBLE = QName.createQName(RM_URI, "recordSearchDispositionEventsEligible");
QName PROP_RS_DISPOSITION_EVENTS = QName.createQName(RM_URI, "recordSearchDispositionEvents"); QName PROP_RS_DISPOSITION_EVENTS = QName.createQName(RM_URI, "recordSearchDispositionEvents");
QName PROP_DISPOSITION_EVENTS = QName.createQName(RM_URI, "dispositionEvents");
QName PROP_RS_VITAL_RECORD_REVIEW_PERIOD = QName.createQName(RM_URI, "recordSearchVitalRecordReviewPeriod"); QName PROP_RS_VITAL_RECORD_REVIEW_PERIOD = QName.createQName(RM_URI, "recordSearchVitalRecordReviewPeriod");
QName PROP_RS_VITAL_RECORD_REVIEW_PERIOD_EXPRESSION = QName.createQName(RM_URI, "recordSearchVitalRecordReviewPeriodExpression"); QName PROP_RS_VITAL_RECORD_REVIEW_PERIOD_EXPRESSION = QName.createQName(RM_URI, "recordSearchVitalRecordReviewPeriodExpression");
QName PROP_RS_DISPOSITION_PERIOD = QName.createQName(RM_URI, "recordSearchDispositionPeriod"); QName PROP_RS_DISPOSITION_PERIOD = QName.createQName(RM_URI, "recordSearchDispositionPeriod");
@@ -248,6 +247,8 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel
QName PROP_RS_HAS_DISPOITION_SCHEDULE = QName.createQName(RM_URI, "recordSearchHasDispositionSchedule"); QName PROP_RS_HAS_DISPOITION_SCHEDULE = QName.createQName(RM_URI, "recordSearchHasDispositionSchedule");
QName PROP_RS_DISPOITION_INSTRUCTIONS = QName.createQName(RM_URI, "recordSearchDispositionInstructions"); QName PROP_RS_DISPOITION_INSTRUCTIONS = QName.createQName(RM_URI, "recordSearchDispositionInstructions");
QName PROP_RS_DISPOITION_AUTHORITY = QName.createQName(RM_URI, "recordSearchDispositionAuthority"); QName PROP_RS_DISPOITION_AUTHORITY = QName.createQName(RM_URI, "recordSearchDispositionAuthority");
QName PROP_RS_DECLASSIFICATION_REVIEW_COMPLETED_AT = QName.createQName(RM_URI, "declassificationReviewCompletedAt");
QName PROP_RS_DECLASSIFICATION_REVIEW_COMPLETED_BY = QName.createQName(RM_URI, "declassificationReviewCompletedBy");
/** @depreacted as of 2.2, because disposable items can now be in multiple holds */ /** @depreacted as of 2.2, because disposable items can now be in multiple holds */
@Deprecated @Deprecated
QName PROP_RS_HOLD_REASON = QName.createQName(RM_URI, "recordSearchHoldReason"); QName PROP_RS_HOLD_REASON = QName.createQName(RM_URI, "recordSearchHoldReason");

View File

@@ -51,6 +51,8 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
*/ */
public class DispositionAbstractBase extends AbstractRmWebScript public class DispositionAbstractBase extends AbstractRmWebScript
{ {
public final static String COMBINE_DISPOSITION_STEP_CONDITIONS = "combineDispositionStepConditions";
/** /**
* Parses the request and providing it's valid returns the DispositionSchedule object. * Parses the request and providing it's valid returns the DispositionSchedule object.
* *

View File

@@ -133,10 +133,10 @@ public class DispositionActionDefinitionPost extends DispositionAbstractBase
json.getBoolean("eligibleOnFirstCompleteEvent") ? "or" : "and"); json.getBoolean("eligibleOnFirstCompleteEvent") ? "or" : "and");
} }
if (json.has("combineDispositionStepConditions")) if (json.has(COMBINE_DISPOSITION_STEP_CONDITIONS))
{ {
props.put(RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS, props.put(RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS,
json.getBoolean("combineDispositionStepConditions")); json.getBoolean(COMBINE_DISPOSITION_STEP_CONDITIONS));
} }
if (json.has("location")) if (json.has("location"))

View File

@@ -131,10 +131,10 @@ public class DispositionActionDefinitionPut extends DispositionAbstractBase
json.getBoolean("eligibleOnFirstCompleteEvent") ? "or" : "and"); json.getBoolean("eligibleOnFirstCompleteEvent") ? "or" : "and");
} }
if (json.has("combineDispositionStepConditions")) if (json.has(COMBINE_DISPOSITION_STEP_CONDITIONS))
{ {
props.put(RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS, props.put(RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS,
json.getBoolean("combineDispositionStepConditions")); json.getBoolean(COMBINE_DISPOSITION_STEP_CONDITIONS));
} }
if (json.has("location")) if (json.has("location"))

View File

@@ -0,0 +1,86 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2018 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.script.slingshot;
import static org.alfresco.model.ContentModel.PROP_NAME;
import static org.alfresco.service.namespace.QName.createQName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
/**
* Method to replace the plain text classification reason id with the correct nodeRef during record search
* @author Ross Gale
* @since 2.7
*/
public class ClassificationReasonsUtil extends SearchUtil
{
public static final String CR_URI = "http://www.alfresco.org/model/securitymarks/1.0";
public static final QName CLASSIFICATION_REASONS_CONTAINER = createQName(CR_URI,"classificationReasonsContainer");
public static final QName PROP_CLASSIFICATION_REASON_CODE = createQName(CR_URI, "classificationReasonCode");
public static final String REASONS_KEY = "clf:classificationReasons:";
/**
* Replace plain text reason id with nodeRef
* @param searchQuery String e.g. clf:classificationReasons:1.4(a)
* @return String e.g. clf:classificationReasons:5cc6d344-fa94-4370-9c81-d947b7e8f2ac
*/
public String replaceReasonWithNodeRef(String searchQuery)
{
List<String> queries = new ArrayList<>(Arrays.asList(searchQuery.split(" ")));
StringBuilder stringBuilder = new StringBuilder();
for (String queryToEdit : queries)
{
if(queryToEdit.contains(REASONS_KEY))
{
for (String reasonId : retrieveAllNodeIds(getRootContainer(CLASSIFICATION_REASONS_CONTAINER)))
{
NodeRef reasonNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, reasonId);
Map<QName, Serializable> properties = nodeService.getProperties(reasonNodeRef);
if (queryToEdit.equals(REASONS_KEY + properties.get(PROP_CLASSIFICATION_REASON_CODE).toString()) ||
queryToEdit.equals(REASONS_KEY +"\""+ properties.get(PROP_CLASSIFICATION_REASON_CODE).toString() + "\""))
{
queryToEdit = REASONS_KEY + properties.get(PROP_NAME).toString();
break;
}
}
}
stringBuilder.append(queryToEdit).append(" ");
}
return stringBuilder.toString();
}
}

View File

@@ -27,6 +27,8 @@
package org.alfresco.module.org_alfresco_module_rm.script.slingshot; package org.alfresco.module.org_alfresco_module_rm.script.slingshot;
import static org.alfresco.module.org_alfresco_module_rm.script.slingshot.ClassificationReasonsUtil.REASONS_KEY;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
@@ -103,6 +105,9 @@ public class RMSearchGet extends DeclarativeWebScript
/** Utility class for record categories */ /** Utility class for record categories */
private RecordCategoryUtil recordCategoryUtil; private RecordCategoryUtil recordCategoryUtil;
/** Utility class for classification reasons (enterprise only) */
private ClassificationReasonsUtil classificationReasonsUtil;
/** /**
* @param recordsManagementSearchService records management search service * @param recordsManagementSearchService records management search service
*/ */
@@ -159,6 +164,11 @@ public class RMSearchGet extends DeclarativeWebScript
this.recordCategoryUtil = recordCategoryUtil; this.recordCategoryUtil = recordCategoryUtil;
} }
public void setClassificationReasonsUtil(ClassificationReasonsUtil classificationReasonsUtil)
{
this.classificationReasonsUtil = classificationReasonsUtil;
}
/** /**
* @param personService person service * @param personService person service
*/ */
@@ -198,6 +208,12 @@ public class RMSearchGet extends DeclarativeWebScript
String filters = req.getParameter(PARAM_FILTERS); String filters = req.getParameter(PARAM_FILTERS);
// TODO this is optional // TODO this is optional
//Replace any plain text reason ids with the appropriate node reference
if(query.contains(REASONS_KEY))
{
query = classificationReasonsUtil.replaceReasonWithNodeRef(query);
}
// Convert into a rm search parameter object // Convert into a rm search parameter object
RecordsManagementSearchParameters searchParameters = RecordsManagementSearchParameters searchParameters =
SavedSearchDetailsCompatibility.createSearchParameters(filters, new String[]{",", "/"}, sortby, namespaceService); SavedSearchDetailsCompatibility.createSearchParameters(filters, new String[]{",", "/"}, sortby, namespaceService);

View File

@@ -0,0 +1,103 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2018 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.script.slingshot;
import static org.alfresco.model.ContentModel.ASSOC_CHILDREN;
import static org.alfresco.model.ContentModel.TYPE_CONTAINER;
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
/**
* Parent class for records search utilities
*
* @author Ross Gale
* @since 2.7
*/
public class SearchUtil
{
/**
* Node service
*/
protected NodeService nodeService;
/**
* Setter for node service
*
* @param nodeService Node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Use a container node ref and return the nodeIds of the contents
*
* @param nodeRef container
* @return list of nodeIds
*/
protected Set<String> retrieveAllNodeIds(NodeRef nodeRef)
{
List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(nodeRef);
return childAssocRefs.stream().map(assoc -> assoc.getChildRef().getId()).collect(Collectors.toSet());
}
/**
* Helper method to get the classification reason root container.
* The method creates the container if it doesn't already exist.
*
* @return reference to the classification reason root container
*/
protected NodeRef getRootContainer(QName container)
{
NodeRef rootNodeRef = nodeService.getRootNode(STORE_REF_WORKSPACE_SPACESSTORE);
List<ChildAssociationRef> assocRefs = nodeService.getChildAssocs(rootNodeRef, ASSOC_CHILDREN, container);
if (assocRefs.isEmpty())
{
return nodeService.createNode(rootNodeRef, ASSOC_CHILDREN, container, TYPE_CONTAINER).getChildRef();
}
else if (assocRefs.size() != 1)
{
throw new AlfrescoRuntimeException("Only one container is allowed.");
}
else
{
return assocRefs.iterator().next().getChildRef();
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2018 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.script.slingshot;
import static org.alfresco.model.ContentModel.ASSOC_CHILDREN;
import static org.alfresco.model.ContentModel.PROP_NAME;
import static org.alfresco.module.org_alfresco_module_rm.script.slingshot.ClassificationReasonsUtil.CLASSIFICATION_REASONS_CONTAINER;
import static org.alfresco.module.org_alfresco_module_rm.script.slingshot.ClassificationReasonsUtil.PROP_CLASSIFICATION_REASON_CODE;
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* @author Ross Gale
* @since 2.7
*/
public class ClassificationReasonsUtilUnitTest
{
@Mock
private NodeService nodeService;
@Mock
private ChildAssociationRef childAssociationRef;
@Mock
private ChildAssociationRef reason;
@Mock
private Map<QName, Serializable> properties;
@InjectMocks
private ClassificationReasonsUtil classificationReasonsUtil;
private NodeRef childNodeRef;
@Before
public void setUp()
{
MockitoAnnotations.initMocks(this);
NodeRef rootNodeRef = new NodeRef("workspace://SpacesStore/rootNodeRef");
NodeRef containerNodeRef = new NodeRef("workspace://SpacesStore/containerNodeRef");
childNodeRef = new NodeRef("workspace://SpacesStore/childNodeRef");
List<ChildAssociationRef> assocRefs = new ArrayList<>();
List<ChildAssociationRef> childAssocRefs = new ArrayList<>();
assocRefs.add(childAssociationRef);
childAssocRefs.add(reason);
when(reason.getChildRef()).thenReturn(childNodeRef);
when(nodeService.getRootNode(STORE_REF_WORKSPACE_SPACESSTORE)).thenReturn(rootNodeRef);
when(nodeService.getChildAssocs(rootNodeRef, ASSOC_CHILDREN, CLASSIFICATION_REASONS_CONTAINER)).thenReturn(assocRefs);
when(childAssociationRef.getChildRef()).thenReturn(containerNodeRef);
when(nodeService.getChildAssocs(containerNodeRef)).thenReturn(childAssocRefs);
}
/**
* Check no modifications are made to non matching parts of the query string
*/
@Test
public void testNoChangeMadeToStringIfKeyNotFound()
{
String stringToTest = "noChangeMadeToString";
assertEquals("Change made to string",stringToTest, classificationReasonsUtil.replaceReasonWithNodeRef(stringToTest).trim());
}
/**
* Check no modifications made if the plain text parameter doesn't have a stored match
*/
@Test
public void testNoChangeMadeToStringIfMatchNotFound()
{
when(nodeService.getProperties(childNodeRef)).thenReturn(properties);
when(properties.get(PROP_CLASSIFICATION_REASON_CODE)).thenReturn("not a match!");
String stringToTest = "clf:classificationReasons:noChangeMadeToString";
assertEquals("Change made to string", stringToTest, classificationReasonsUtil.replaceReasonWithNodeRef(stringToTest).trim());
}
/**
* Check the query is updated correctly when a match is found
*/
@Test
public void testChangeMadeToStringIfMatchFound()
{
when(nodeService.getProperties(childNodeRef)).thenReturn(properties);
when(properties.get(PROP_CLASSIFICATION_REASON_CODE)).thenReturn("stringToChange");
when(properties.get(PROP_NAME)).thenReturn("newString");
String stringToTest = "clf:classificationReasons:stringToChange";
assertEquals("No change made to string", "clf:classificationReasons:newString", classificationReasonsUtil.replaceReasonWithNodeRef(stringToTest).trim());
}
}