diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties index d8ae638a63..bc4d3493e5 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties @@ -13,5 +13,9 @@ log4j.logger.org.alfresco.module.org_alfresco_module_rm.patch=info # RM Permission Debug Information # #log4j.logger.org.alfresco.module.org_alfresco_module_rm.capability.RMEntryVoter=debug +#log4j.logger.org.alfresco.module.org_alfresco_module_rm.capability.RMAfterInvocationProvider=debug #log4j.logger.org.alfresco.module.org_alfresco_module_rm.capability.declarative=debug -#log4j.logger.org.alfresco.module.org_alfresco_module_rm.record.RecordServiceImpl=debug \ No newline at end of file +#log4j.logger.org.alfresco.module.org_alfresco_module_rm.record.RecordServiceImpl=debug +#log4j.logger.org.springframework.extensions.webscripts.ScriptDebugger=on + +log4j.logger.org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService=debug \ No newline at end of file diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties index 207b7db780..4a7a24c7e9 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties @@ -133,6 +133,31 @@ createDispositionSchedule.description=Create disposition schedule # File Destruction Report fileDestructionReport.title=File destruction report fileDestructionReport.description=File destruction report +# Cut off +cutoff.title=Cut off +cutoff.description=Cut off +# Destroy +destroy.title=Destroy +destroy.description=Destroy +# Reviewed +reviewed.title=Reviewed +reviewed.description=Reviewed +# Hide Record +hide-record.title=Hide record +hide-record.description=Hide record +# Transfer +transfer.title=Transfer +transfer.description=Transfer +# Uncut off +unCutoff.title=Uncut off +unCutoff.description=Uncut off +# Accession +accession.title=Accession +accession.description=Accession +# Retain +retain.title=Retain +retain.description=Retain + # Action parameter constraints rm-ac-is-kind-kinds.record_category=Record Category diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/audit-service.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/audit-service.properties index e1b89c541c..db7e89fb2e 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/audit-service.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/audit-service.properties @@ -3,35 +3,4 @@ rm.audit.created-object=Created Object rm.audit.delte-object=Delete Object rm.audit.login-succeeded=Login Succeeded rm.audit.login-failed=Login Failed -rm.audit.filed-record=Filed Record -rm.audit.reviewed=Reviewed -rm.audit.cut-off=Cut Off -rm.audit.reversed-cut-off=Reversed Cut Off -rm.audit.destroyed-item=Destroyed Item -rm.audit.opened-record-folder=Opened Record Folder -rm.audit.closed-record-folder=Closed Record Folder -rm.audit.setup-recorder-folder=Setup Recorder Folder -rm.audit.declared-record=Complete Record -rm.audit.undeclared-record=Reopen Record -rm.audit.froze-item=Froze Item -rm.audit.relinquised-hold=Relinquished Hold -rm.audit.updated-hold-reason=Updated Hold Reason -rm.audit.updated-review-as-of-date=Updated Review As Of Date -rm.audit.updated-disposition-as-of-date=Updated Disposition As Of Date -rm.audit.updated-vital-record-definition=Updated Vital Record Definition -rm.audit.updated-disposition-action-definition=Updated Disposition Action Definition -rm.audit.completed-event=Completed Event -rm.audit.revered-complete-event=Reversed Completed Event -rm.audit.transferred-item=Transferred Item -rm.audit.completed-transfer=Completed Transfer -rm.audit.accession=Accession -rm.audit.copmleted-accession=Completed Accession -rm.audit.scanned-record=Set Record as a Scanned Record -rm.audit.pdf-record=Set Record as a PDF Record -rm.audit.photo-record=Set Record as a Digital Photographic Record -rm.audit.web-record=Set Record as a Web Record -rm.audit.trail-file-fail=Failed to generate audit trail file. -rm.audit.audit-report=Audit Report -rm.audit.create-disposition-schedule=Create Disposition Schedule -rm.audit.unfreeze=Unfreeze -rm.audit.reject-record=Reject Record \ No newline at end of file +rm.audit.create-person=Create Person \ No newline at end of file diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml index 6d382917ad..a1a0547ddf 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml @@ -43,35 +43,12 @@ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml index ff2fda6a24..949ee1fb7c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml @@ -582,6 +582,7 @@ parent="rmAction"> + @@ -612,6 +613,7 @@ class="org.alfresco.module.org_alfresco_module_rm.action.impl.BroadcastDispositionActionDefinitionUpdateAction" parent="rmAction" > + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-audit-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-audit-context.xml new file mode 100755 index 0000000000..4bfe025435 --- /dev/null +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-audit-context.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index 9d598677c9..c81aae76ae 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -966,6 +966,8 @@ + + @@ -1081,19 +1083,15 @@ . + */ +package org.alfresco.module.org_alfresco_module_rm.audit; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Deprecated records management audit interface methods. + * + * @author Roy Wetherall + * @since 2.1 + */ +public interface RecordsManagementAuditServiceDeprecated +{ + /** + * @deprecated as of 2.1, see {@link #stop(NodeRef)} + */ + @Deprecated + void stop(); + + /** + * @deprecated as of 2.1, see {@link #clear(NodeRef)} + */ + @Deprecated + void clear(); + + /** + * @deprecated as of 2.1, see {@link #isEnabled(NodeRef)} + */ + @Deprecated + boolean isEnabled(); + + /** + * @deprecated as of 2.1, see {@link #getDateLastStarted(NodeRef)} + */ + @Deprecated + Date getDateLastStarted(); + + /** + * @deprecated as of 2.1, see {@link #getDateLastStopped(NodeRef)} + */ + Date getDateLastStopped(); + + /** + * @deprecated as of 2.1 + */ + @Deprecated + void auditRMAction(RecordsManagementAction action, NodeRef nodeRef, Map parameters); + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/AuditableActionExecuterAbstractBase.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/AuditableActionExecuterAbstractBase.java new file mode 100755 index 0000000000..116fb945fb --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/AuditableActionExecuterAbstractBase.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2013 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.action; + +import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Auditable action executer abstract base + * + * @author Roy Wetherall + * @since 2.1 + */ +public abstract class AuditableActionExecuterAbstractBase extends ActionExecuterAbstractBase implements ApplicationContextAware +{ + /** Indicates whether the action is auditable or not */ + protected boolean auditable = true; + + protected ApplicationContext applicationContext; + + private RecordsManagementAuditService auditService; + + public void setAuditable(boolean auditable) + { + this.auditable = auditable; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + private RecordsManagementAuditService getAuditService() + { + if (auditService == null) + { + auditService = (RecordsManagementAuditService)applicationContext.getBean("recordsManagementAuditService"); + } + return auditService; + } + + @Override + public void init() + { + super.init(); + + if (auditable == true) + { + getAuditService().registerAuditEvent(this.getActionDefinition().getName(), this.getActionDefinition().getTitle()); + } + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#execute(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + public void execute(Action action, NodeRef actionedUponNodeRef) + { + // execute the action + super.execute(action, actionedUponNodeRef); + + // audit the execution of the action + if (auditable == true) + { + getAuditService().auditEvent(actionedUponNodeRef, this.getActionDefinition().getName()); + } + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/PropertySubActionExecuterAbstractBase.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/PropertySubActionExecuterAbstractBase.java index fa242308e3..9aa0ce5c86 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/PropertySubActionExecuterAbstractBase.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/PropertySubActionExecuterAbstractBase.java @@ -18,7 +18,6 @@ */ package org.alfresco.module.org_alfresco_module_rm.action; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.repo.action.parameter.ParameterProcessorComponent; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; @@ -26,28 +25,42 @@ import org.alfresco.service.cmr.repository.NodeRef; /** * Extension to action implementation hierarchy to insert parameter substitution processing. * + * NOTE: this should eventually be pushed into the core. + * * @author Roy Wetherall * @since 2.1 */ -public abstract class PropertySubActionExecuterAbstractBase extends ActionExecuterAbstractBase +public abstract class PropertySubActionExecuterAbstractBase extends AuditableActionExecuterAbstractBase { + /** Parameter processor component */ private ParameterProcessorComponent parameterProcessorComponent; + /** Indicates whether parameter substitutions are allowed */ protected boolean allowParameterSubstitutions = false; + /** + * @param parameterProcessorComponent parameter processor component + */ public void setParameterProcessorComponent(ParameterProcessorComponent parameterProcessorComponent) { this.parameterProcessorComponent = parameterProcessorComponent; } + /** + * @param allowParameterSubstitutions true if property subs allowed, false otherwise + */ public void setAllowParameterSubstitutions(boolean allowParameterSubstitutions) { this.allowParameterSubstitutions = allowParameterSubstitutions; } + /** + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#execute(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ @Override public void execute(Action action, NodeRef actionedUponNodeRef) { + // do the property subs (if any exist) if (allowParameterSubstitutions == true) { parameterProcessorComponent.process(action, getActionDefinition(), actionedUponNodeRef); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/RMActionExecuterAbstractBase.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/RMActionExecuterAbstractBase.java index 10d9c2365b..10a49b6096 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/RMActionExecuterAbstractBase.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/RMActionExecuterAbstractBase.java @@ -324,6 +324,8 @@ public abstract class RMActionExecuterAbstractBase extends PropertySubActionExe PropertyCheck.mandatory(this, "recordsManagementService", recordsManagementService); PropertyCheck.mandatory(this, "recordsManagementAdminService", recordsManagementAdminService); PropertyCheck.mandatory(this, "recordsManagementEventService", recordsManagementEventService); + + super.init(); } /** @@ -405,8 +407,6 @@ public abstract class RMActionExecuterAbstractBase extends PropertySubActionExe Action action = this.actionService.createAction(name); action.setParameterValues(parameters); - recordsManagementAuditService.auditRMAction(this, filePlanComponent, parameters); - // Execute the action this.actionService.executeAction(action, filePlanComponent); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java index bdcabf8ae8..a8ff822392 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java @@ -22,12 +22,12 @@ import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.action.AuditableActionExecuterAbstractBase; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService; import org.alfresco.repo.action.ParameterDefinitionImpl; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; @@ -45,7 +45,7 @@ import org.apache.commons.logging.LogFactory; * * @author Roy Wetherall */ -public class CreateRecordAction extends ActionExecuterAbstractBase +public class CreateRecordAction extends AuditableActionExecuterAbstractBase implements RecordsManagementModel { /** Logger */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/HideRecordAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/HideRecordAction.java index fdb3924ce9..2e9af8715e 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/HideRecordAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/HideRecordAction.java @@ -20,9 +20,9 @@ package org.alfresco.module.org_alfresco_module_rm.action.dm; import java.util.List; +import org.alfresco.module.org_alfresco_module_rm.action.AuditableActionExecuterAbstractBase; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.repository.NodeRef; @@ -38,7 +38,8 @@ import org.apache.commons.logging.LogFactory; * @author Tuna Aksoy * @since 2.1 */ -public class HideRecordAction extends ActionExecuterAbstractBase implements RecordsManagementModel +public class HideRecordAction extends AuditableActionExecuterAbstractBase + implements RecordsManagementModel { /** Logger */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuditEvent.java deleted file mode 100644 index 0cd942fd5c..0000000000 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuditEvent.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 . - */ -package org.alfresco.module.org_alfresco_module_rm.audit; - -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Class to represent an audit event - * - * @author Gavin Cornwell - */ -public class AuditEvent -{ - private final String name; - private final String label; - - /** - * Constructor - * - * @param name The audit event name - * @param label The audit event label (or I18N lookup id) - */ - public AuditEvent(String name, String label) - { - this.name = name; - - String lookup = I18NUtil.getMessage(label); - if (lookup != null) - { - label = lookup; - } - this.label = label; - } - - /** - * - * @return The audit event name - */ - public String getName() - { - return this.name; - } - - /** - * - * @return The audit event label - */ - public String getLabel() - { - return this.label; - } -} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditService.java index 1344476983..091924be6c 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditService.java @@ -24,21 +24,19 @@ import java.util.Date; import java.util.List; import java.util.Map; -import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction; +import org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; /** * Records management audit service. * * @author Gavin Cornwell */ -public interface RecordsManagementAuditService +public interface RecordsManagementAuditService extends RecordsManagementAuditServiceDeprecated { public enum ReportFormat { HTML, JSON } - public static final String RM_AUDIT_EVENT_UPDATE_RM_OBJECT = "Update RM Object"; - public static final String RM_AUDIT_EVENT_CREATE_RM_OBJECT = "Create RM Object"; - public static final String RM_AUDIT_EVENT_DELETE_RM_OBJECT = "Delete RM Object"; public static final String RM_AUDIT_EVENT_LOGIN_SUCCESS = "Login.Success"; public static final String RM_AUDIT_EVENT_LOGIN_FAILURE = "Login.Failure"; @@ -68,10 +66,67 @@ public interface RecordsManagementAuditService public static final String RM_AUDIT_DATA_LOGIN_ERROR = "/RM/login/error/value"; /** - * @deprecated as of 2.1, see {@link #start(NodeRef)} + * Retrieves a list of audit events. + * + * @return List of audit events */ - @Deprecated - void start(); + List getAuditEvents(); + + /** + * Register audit event + * + * @param name name of audit event + * @param label display label of audit event + */ + void registerAuditEvent(String name, String label); + + /** + * + * @param auditEvent + */ + void registerAuditEvent(AuditEvent auditEvent); + + /** + * + * @param nodeRef + * @param eventName + */ + void auditEvent(NodeRef nodeRef, + String eventName); + + /** + * + * @param nodeRef + * @param eventName + * @param before + * @param after + */ + void auditEvent(NodeRef nodeRef, + String eventName, + Map before, + Map after); + + /** + * + * @param nodeRef + * @param eventName + * @param before + * @param after + * @param immediate + */ + void auditEvent(NodeRef nodeRef, + String eventName, + Map before, + Map after, + boolean immediate); + + /** + * Determines whether the RM audit log is currently enabled. + * + * @param filePlan file plan + * @return true if RM auditing is active false otherwise + */ + boolean isAuditLogEnabled(NodeRef filePlan); /** * Start RM auditing. @@ -79,13 +134,7 @@ public interface RecordsManagementAuditService * @param filePlan file plan */ void startAuditLog(NodeRef filePlan); - - /** - * @deprecated as of 2.1, see {@link #stop(NodeRef)} - */ - @Deprecated - void stop(); - + /** * Stop RM auditing. * @@ -93,11 +142,6 @@ public interface RecordsManagementAuditService */ void stopAuditLog(NodeRef filePlan); - /** - * @deprecated as of 2.1, see {@link #clear(NodeRef)} - */ - @Deprecated - void clear(); /** * Clears the RM audit. @@ -106,26 +150,6 @@ public interface RecordsManagementAuditService */ void clearAuditLog(NodeRef filePlan); - /** - * @deprecated as of 2.1, see {@link #isEnabled(NodeRef)} - */ - @Deprecated - boolean isEnabled(); - - /** - * Determines whether the RM audit log is currently enabled. - * - * @param filePlan file plan - * @return true if RM auditing is active false otherwise - */ - boolean isAuditLogEnabled(NodeRef filePlan); - - /** - * @deprecated as of 2.1, see {@link #getDateLastStarted(NodeRef)} - */ - @Deprecated - Date getDateLastStarted(); - /** * Returns the date the RM audit was last started. * @@ -133,12 +157,7 @@ public interface RecordsManagementAuditService * @return Date the audit was last started */ Date getDateAuditLogLastStarted(NodeRef filePlan); - - /** - * @deprecated as of 2.1, see {@link #getDateLastStopped(NodeRef)} - */ - Date getDateLastStopped(); - + /** * Returns the date the RM audit was last stopped. * @@ -146,15 +165,6 @@ public interface RecordsManagementAuditService */ Date getDateAuditLogLastStopped(NodeRef filePlan); - /** - * An explicit call that RM actions can make to have the events logged. - * - * @param action the action that will be performed - * @param nodeRef the component being acted on - * @param parameters the action's parameters - */ - void auditRMAction(RecordsManagementAction action, NodeRef nodeRef, Map parameters); - /** * Retrieves a list of audit log entries using the provided parameters * represented by the RecordsManagementAuditQueryParameters instance. @@ -201,18 +211,4 @@ public interface RecordsManagementAuditService * @return NodeRef of the undeclared record filed */ NodeRef fileAuditTrailAsRecord(RecordsManagementAuditQueryParameters params, NodeRef destination, ReportFormat format); - - /** - * Retrieves a list of audit events. - * - * @return List of audit events - */ - List getAuditEvents(); - - /** - * Register records management action - * - * @param rmAction records management action - */ - void register(RecordsManagementAction rmAction); } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditServiceImpl.java index 8c8a1a5991..3fcbf8371b 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/RecordsManagementAuditServiceImpl.java @@ -29,24 +29,21 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; +import org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; -import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.repo.audit.AuditComponent; import org.alfresco.repo.audit.model.AuditApplication; import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; -import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; -import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; -import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -59,7 +56,6 @@ import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; @@ -90,19 +86,10 @@ import org.springframework.extensions.surf.util.ParameterCheck; * @author Gavin Cornwell * @since 3.2 */ -public class RecordsManagementAuditServiceImpl - extends AbstractLifecycleBean - implements RecordsManagementAuditService, - NodeServicePolicies.OnCreateNodePolicy, - NodeServicePolicies.BeforeDeleteNodePolicy, - NodeServicePolicies.OnUpdatePropertiesPolicy +public class RecordsManagementAuditServiceImpl extends AbstractLifecycleBean + implements RecordsManagementAuditService { /** I18N */ - private static final String MSG_UPDATED_METADATA = "rm.audit.updated-metadata"; - private static final String MSG_CREATED_OBJECT = "rm.audit.created-object"; - private static final String MSG_DELETE_OBJECT = "rm.audit.delte-object"; - private static final String MSG_LOGIN_SUCCEEDED = "rm.audit.login-succeeded"; - private static final String MSG_LOGIN_FAILED = "rm.audit.login-failed"; private static final String MSG_TRAIL_FILE_FAIL = "rm.audit.trail-file-fail"; private static final String MSG_AUDIT_REPORT = "rm.audit.audit-report"; @@ -128,7 +115,7 @@ public class RecordsManagementAuditServiceImpl private boolean shutdown = false; - private RMAuditTxnListener txnListener; + private RMAuditTxnListener txnListener = new RMAuditTxnListener(); /** Registered and initialised records management auditEvents */ private Map auditEvents = new HashMap(); @@ -213,15 +200,21 @@ public class RecordsManagementAuditServiceImpl this.filePlanService = filePlanService; } - /** - * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementAuditService#register(org.alfresco.module.org_alfresco_module_rm.RecordsManagementAction) - */ - public void register(RecordsManagementAction rmAction) + @Override + public void registerAuditEvent(String name, String label) { - if (this.auditEvents.containsKey(rmAction.getName()) == false) + registerAuditEvent(new AuditEvent(name, label)); + } + + @Override + public void registerAuditEvent(AuditEvent auditEvent) + { + if (logger.isDebugEnabled() == true) { - this.auditEvents.put(rmAction.getName(), new AuditEvent(rmAction.getName(), rmAction.getLabel())); + logger.debug("Registering audit event " + auditEvent.getName()); } + + this.auditEvents.put(auditEvent.getName(), auditEvent); } /** @@ -239,52 +232,12 @@ public class RecordsManagementAuditServiceImpl PropertyCheck.mandatory(this, "rmActionService", rmActionService); PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); PropertyCheck.mandatory(this, "filePlanService", filePlanService); - - // setup the audit events map - initAuditEvents(); - } - - protected void initAuditEvents() - { - - this.auditEvents.put(RM_AUDIT_EVENT_UPDATE_RM_OBJECT, - new AuditEvent(RM_AUDIT_EVENT_UPDATE_RM_OBJECT, MSG_UPDATED_METADATA)); - this.auditEvents.put(RM_AUDIT_EVENT_CREATE_RM_OBJECT, - new AuditEvent(RM_AUDIT_EVENT_CREATE_RM_OBJECT, MSG_CREATED_OBJECT)); - this.auditEvents.put(RM_AUDIT_EVENT_DELETE_RM_OBJECT, - new AuditEvent(RM_AUDIT_EVENT_DELETE_RM_OBJECT, MSG_DELETE_OBJECT)); - this.auditEvents.put(RM_AUDIT_EVENT_LOGIN_SUCCESS, - new AuditEvent(RM_AUDIT_EVENT_LOGIN_SUCCESS, MSG_LOGIN_SUCCEEDED)); - this.auditEvents.put(RM_AUDIT_EVENT_LOGIN_FAILURE, - new AuditEvent(RM_AUDIT_EVENT_LOGIN_FAILURE, MSG_LOGIN_FAILED)); - - // Added for DOD compliance - this.auditEvents.put("createPerson", - new AuditEvent("createPerson", "User Created")); } @Override protected void onBootstrap(ApplicationEvent event) { shutdown = false; - txnListener = new RMAuditTxnListener(); - // Register to listen for property changes to rma:record types - policyComponent.bindClassBehaviour( - OnUpdatePropertiesPolicy.QNAME, - RecordsManagementModel.ASPECT_RECORD_COMPONENT_ID, - new JavaBehaviour(this, "onUpdateProperties")); - policyComponent.bindClassBehaviour( - OnCreateNodePolicy.QNAME, - RecordsManagementModel.ASPECT_RECORD_COMPONENT_ID, - new JavaBehaviour(this, "onCreateNode")); - policyComponent.bindClassBehaviour( - BeforeDeleteNodePolicy.QNAME, - RecordsManagementModel.ASPECT_RECORD_COMPONENT_ID, - new JavaBehaviour(this, "beforeDeleteNode")); - policyComponent.bindClassBehaviour( - OnCreateNodePolicy.QNAME, - ContentModel.TYPE_PERSON, - new JavaBehaviour(this, "onCreatePersonNode")); } @Override @@ -293,25 +246,21 @@ public class RecordsManagementAuditServiceImpl shutdown = true; } - /*** TODO remove this when we support multiple file plans ****/ - - private NodeRef defaultFilePlan; - + /** + * Helper method to get the default file plan + * + * @return NodRef default file plan + */ private NodeRef getDefaultFilePlan() { + NodeRef defaultFilePlan = filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); if (defaultFilePlan == null) - { - defaultFilePlan = filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); - if (defaultFilePlan == null) - { - throw new AlfrescoRuntimeException("Default file plan could not be found."); - } - } + { + throw new AlfrescoRuntimeException("Default file plan could not be found."); + } return defaultFilePlan; } - /*** TODO end ***/ - /** * {@inheritDoc} */ @@ -493,54 +442,37 @@ public class RecordsManagementAuditServiceImpl } } - public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) - { - auditRMEvent(nodeRef, RM_AUDIT_EVENT_UPDATE_RM_OBJECT, before, after); - } - - public void beforeDeleteNode(NodeRef nodeRef) - { - auditRMEvent(nodeRef, RM_AUDIT_EVENT_DELETE_RM_OBJECT, null, null); - } - - public void onCreateNode(ChildAssociationRef childAssocRef) - { - auditRMEvent(childAssocRef.getChildRef(), RM_AUDIT_EVENT_CREATE_RM_OBJECT, null, null); - } - - public void onCreatePersonNode(ChildAssociationRef childAssocRef) - { - auditRMEvent(childAssocRef.getChildRef(), "createPerson", null, null); - } - /** * {@inheritDoc} * @since 3.2 + * @deprecated since 2.1 */ + @Deprecated public void auditRMAction( RecordsManagementAction action, NodeRef nodeRef, Map parameters) { - auditRMEvent(nodeRef, action.getName(), null, null); + auditEvent(nodeRef, action.getName()); } - - /** - * Audit an event for a node - * - * @param nodeRef the node to which the event applies - * @param eventName the name of the event - * @param nodePropertiesBefore properties before the event (optional) - * @param nodePropertiesAfter properties after the event (optional) - */ - private void auditRMEvent( - NodeRef nodeRef, - String eventName, - Map nodePropertiesBefore, - Map nodePropertiesAfter) + + @Override + public void auditEvent(NodeRef nodeRef, String eventName) { - // If we are deleting nodes, then we need to audit NOW - if (eventName.equals(RecordsManagementAuditService.RM_AUDIT_EVENT_DELETE_RM_OBJECT)) + auditEvent(nodeRef, eventName, null, null, false); + } + + @Override + public void auditEvent(NodeRef nodeRef, String eventName, Map before, Map after) + { + auditEvent(nodeRef, eventName, before, after, false); + } + + @Override + public void auditEvent(NodeRef nodeRef, String eventName, Map before, Map after, boolean immediate) + { + // deal with immediate auditing if required + if (immediate == true) { // Deleted nodes will not be available at the end of the transaction. The data needs to // be extracted now and the audit entry needs to be created now. @@ -555,16 +487,20 @@ public class RecordsManagementAuditServiceImpl else { // Create an event for auditing post-commit - Map auditedNodes = TransactionalResourceHelper.getMap(KEY_RM_AUDIT_NODE_RECORDS); - RMAuditNode auditedNode = auditedNodes.get(nodeRef); - if (auditedNode == null) + Map> auditedNodes = TransactionalResourceHelper.getMap(KEY_RM_AUDIT_NODE_RECORDS); + Set auditDetails = auditedNodes.get(nodeRef); + if (auditDetails == null) { - auditedNode = new RMAuditNode(); - auditedNodes.put(nodeRef, auditedNode); + auditDetails = new HashSet(3); + auditedNodes.put(nodeRef, auditDetails); + // Bind the listener to the txn. We could do it anywhere in the method, this position ensures // that we avoid some rebinding of the listener AlfrescoTransactionSupport.bindListener(txnListener); } + + RMAuditNode auditedNode = new RMAuditNode(); + // Only update the eventName if it has not already been done if (auditedNode.getEventName() == null) { @@ -573,15 +509,17 @@ public class RecordsManagementAuditServiceImpl // Set the properties before the start if they are not already available if (auditedNode.getNodePropertiesBefore() == null) { - auditedNode.setNodePropertiesBefore(nodePropertiesBefore); + auditedNode.setNodePropertiesBefore(before); } // Set the after values if they are provided. // Overwrite as we assume that these represent the latest state of the node. - if (nodePropertiesAfter != null) + if (after != null) { - auditedNode.setNodePropertiesAfter(nodePropertiesAfter); + auditedNode.setNodePropertiesAfter(after); } // That is it. The values are queued for the end of the transaction. + + auditDetails.add(auditedNode); } } @@ -634,7 +572,7 @@ public class RecordsManagementAuditServiceImpl @Override public void afterCommit() { - final Map auditedNodes = TransactionalResourceHelper.getMap(KEY_RM_AUDIT_NODE_RECORDS); + final Map> auditedNodes = TransactionalResourceHelper.getMap(KEY_RM_AUDIT_NODE_RECORDS); // Start a *new* read-write transaction to audit in RetryingTransactionCallback auditCallback = new RetryingTransactionCallback() @@ -653,11 +591,11 @@ public class RecordsManagementAuditServiceImpl * * @param auditedNodes details of the nodes that were modified */ - private void auditInTxn(Map auditedNodes) throws Throwable + private void auditInTxn(Map> auditedNodes) throws Throwable { // Go through all the audit information and audit it boolean auditedSomething = false; // We rollback if nothing is audited - for (Map.Entry entry : auditedNodes.entrySet()) + for (Map.Entry> entry : auditedNodes.entrySet()) { NodeRef nodeRef = entry.getKey(); @@ -667,63 +605,66 @@ public class RecordsManagementAuditServiceImpl continue; } - RMAuditNode auditedNode = entry.getValue(); - - // Action description - String eventName = auditedNode.getEventName(); - - Map auditMap = buildAuditMap(nodeRef, eventName); - - // TODO do we care if the before and after are null?? - - // Property changes - Map propertiesBefore = auditedNode.getNodePropertiesBefore(); - Map propertiesAfter = auditedNode.getNodePropertiesAfter(); - Pair, Map> deltaPair = - PropertyMap.getBeforeAndAfterMapsForChanges(propertiesBefore, propertiesAfter); - auditMap.put( - AuditApplication.buildPath( - RecordsManagementAuditService.RM_AUDIT_SNIPPET_EVENT, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_NODE, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_CHANGES, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_BEFORE), - (Serializable) deltaPair.getFirst()); - auditMap.put( - AuditApplication.buildPath( - RecordsManagementAuditService.RM_AUDIT_SNIPPET_EVENT, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_NODE, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_CHANGES, - RecordsManagementAuditService.RM_AUDIT_SNIPPET_AFTER), - (Serializable) deltaPair.getSecond()); - - // Audit it - if (logger.isDebugEnabled()) - { - logger.debug("RM Audit: Auditing values: \n" + auditMap); - } - auditMap = auditComponent.recordAuditValues(RecordsManagementAuditService.RM_AUDIT_PATH_ROOT, auditMap); - if (auditMap.isEmpty()) + Set auditedNodeDetails = entry.getValue(); + + for (RMAuditNode auditedNode : auditedNodeDetails) { + // Action description + String eventName = auditedNode.getEventName(); + + Map auditMap = buildAuditMap(nodeRef, eventName); + + // TODO do we care if the before and after are null?? + + // Property changes + Map propertiesBefore = auditedNode.getNodePropertiesBefore(); + Map propertiesAfter = auditedNode.getNodePropertiesAfter(); + Pair, Map> deltaPair = + PropertyMap.getBeforeAndAfterMapsForChanges(propertiesBefore, propertiesAfter); + auditMap.put( + AuditApplication.buildPath( + RecordsManagementAuditService.RM_AUDIT_SNIPPET_EVENT, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_NODE, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_CHANGES, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_BEFORE), + (Serializable) deltaPair.getFirst()); + auditMap.put( + AuditApplication.buildPath( + RecordsManagementAuditService.RM_AUDIT_SNIPPET_EVENT, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_NODE, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_CHANGES, + RecordsManagementAuditService.RM_AUDIT_SNIPPET_AFTER), + (Serializable) deltaPair.getSecond()); + + // Audit it if (logger.isDebugEnabled()) { - logger.debug("RM Audit: Nothing was audited."); + logger.debug("RM Audit: Auditing values: \n" + auditMap); } - } - else - { - if (logger.isDebugEnabled()) + auditMap = auditComponent.recordAuditValues(RecordsManagementAuditService.RM_AUDIT_PATH_ROOT, auditMap); + if (auditMap.isEmpty()) { - logger.debug("RM Audit: Audited values: \n" + auditMap); + if (logger.isDebugEnabled()) + { + logger.debug("RM Audit: Nothing was audited."); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("RM Audit: Audited values: \n" + auditMap); + } + // We must commit the transaction to get the values in + auditedSomething = true; } - // We must commit the transaction to get the values in - auditedSomething = true; } - } - // Check if anything was audited - if (!auditedSomething) - { - // Nothing was audited, so do nothing - RetryingTransactionHelper.getActiveUserTransaction().setRollbackOnly(); + // Check if anything was audited + if (!auditedSomething) + { + // Nothing was audited, so do nothing + RetryingTransactionHelper.getActiveUserTransaction().setRollbackOnly(); + } } } } @@ -849,18 +790,16 @@ public class RecordsManagementAuditServiceImpl if (values.containsKey(RecordsManagementAuditService.RM_AUDIT_DATA_EVENT_NAME)) { // This data is /RM/event/... - eventName = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_EVENT_NAME); - fullName = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_PERSON_FULLNAME); - userRoles = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_PERSON_ROLES); - nodeRef = (NodeRef) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_NODE_NODEREF); - nodeName = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_NODE_NAME); - QName nodeTypeQname = (QName) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_NODE_TYPE); - nodeIdentifier = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_NODE_IDENTIFIER); - namePath = (String) values.get(RecordsManagementAuditService.RM_AUDIT_DATA_NODE_NAMEPATH); - beforeProperties = (Map) values.get( - RecordsManagementAuditService.RM_AUDIT_DATA_NODE_CHANGES_BEFORE); - afterProperties = (Map) values.get( - RecordsManagementAuditService.RM_AUDIT_DATA_NODE_CHANGES_AFTER); + eventName = (String) values.get(RM_AUDIT_DATA_EVENT_NAME); + fullName = (String) values.get(RM_AUDIT_DATA_PERSON_FULLNAME); + userRoles = (String) values.get(RM_AUDIT_DATA_PERSON_ROLES); + nodeRef = (NodeRef) values.get(RM_AUDIT_DATA_NODE_NODEREF); + nodeName = (String) values.get(RM_AUDIT_DATA_NODE_NAME); + QName nodeTypeQname = (QName) values.get(RM_AUDIT_DATA_NODE_TYPE); + nodeIdentifier = (String) values.get(RM_AUDIT_DATA_NODE_IDENTIFIER); + namePath = (String) values.get(RM_AUDIT_DATA_NODE_NAMEPATH); + beforeProperties = (Map) values.get(RM_AUDIT_DATA_NODE_CHANGES_BEFORE); + afterProperties = (Map) values.get(RM_AUDIT_DATA_NODE_CHANGES_AFTER); // Convert some of the values to recognizable forms nodeType = null; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/AuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/AuditEvent.java new file mode 100644 index 0000000000..d5fa10cf11 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/AuditEvent.java @@ -0,0 +1,105 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.audit.event; + +import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService; +import org.alfresco.repo.policy.PolicyComponent; +import org.springframework.extensions.surf.util.I18NUtil; + + +/** + * Class to represent an audit event + * + * @author Gavin Cornwell + * @author Roy Wetherall + */ +public class AuditEvent +{ + /** Name */ + protected String name; + + /** Label */ + protected String label; + + protected RecordsManagementAuditService recordsManagementAuditService; + + protected PolicyComponent policyComponent; + + public void setRecordsManagementAuditService(RecordsManagementAuditService recordsManagementAuditService) + { + this.recordsManagementAuditService = recordsManagementAuditService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void init() + { + recordsManagementAuditService.registerAuditEvent(this); + } + + public AuditEvent() + { + } + + public AuditEvent(String name, String label) + { + this.name = name; + this.label = label; + } + + /** + * @return audit event name + */ + public String getName() + { + return this.name; + } + + /** + * @param name audit event name + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @return audit event label + */ + public String getLabel() + { + String lookup = I18NUtil.getMessage(label); + if (lookup == null) + { + lookup = label; + } + return lookup; + } + + /** + * @param label audit event label + */ + public void setLabel(String label) + { + this.label = label; + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreateObjectAuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreateObjectAuditEvent.java new file mode 100755 index 0000000000..77797db62c --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreateObjectAuditEvent.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.audit.event; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.service.cmr.repository.ChildAssociationRef; + +public class CreateObjectAuditEvent extends AuditEvent implements OnCreateNodePolicy +{ + /** + * @see org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent#init() + */ + @Override + public void init() + { + super.init(); + + policyComponent.bindClassBehaviour( + OnCreateNodePolicy.QNAME, + RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT, + new JavaBehaviour(this, "onCreateNode")); + } + + /** + * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy#onCreateNode(org.alfresco.service.cmr.repository.ChildAssociationRef) + */ + public void onCreateNode(ChildAssociationRef childAssocRef) + { + recordsManagementAuditService.auditEvent(childAssocRef.getChildRef(), name); + } + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreatePersonAuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreatePersonAuditEvent.java new file mode 100755 index 0000000000..a7b82dcf27 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/CreatePersonAuditEvent.java @@ -0,0 +1,45 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.audit.event; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.service.cmr.repository.ChildAssociationRef; + +public class CreatePersonAuditEvent extends AuditEvent implements OnCreateNodePolicy +{ + + @Override + public void init() + { + super.init(); + + policyComponent.bindClassBehaviour( + OnCreateNodePolicy.QNAME, + ContentModel.TYPE_PERSON, + new JavaBehaviour(this, "onCreateNode")); + } + + public void onCreateNode(ChildAssociationRef childAssocRef) + { + recordsManagementAuditService.auditEvent(childAssocRef.getChildRef(), name); + } + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteObjectAuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteObjectAuditEvent.java new file mode 100755 index 0000000000..60cd0df69f --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteObjectAuditEvent.java @@ -0,0 +1,44 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.audit.event; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.service.cmr.repository.NodeRef; + +public class DeleteObjectAuditEvent extends AuditEvent implements BeforeDeleteNodePolicy +{ + @Override + public void init() + { + super.init(); + + policyComponent.bindClassBehaviour( + BeforeDeleteNodePolicy.QNAME, + RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT, + new JavaBehaviour(this, "beforeDeleteNode")); + } + + public void beforeDeleteNode(NodeRef nodeRef) + { + recordsManagementAuditService.auditEvent(nodeRef, name, null, null, true); + } + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/UpdateObjectAuditEvent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/UpdateObjectAuditEvent.java new file mode 100755 index 0000000000..0760dcc467 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/UpdateObjectAuditEvent.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ +package org.alfresco.module.org_alfresco_module_rm.audit.event; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public class UpdateObjectAuditEvent extends AuditEvent implements OnUpdatePropertiesPolicy +{ + + @Override + public void init() + { + super.init(); + + policyComponent.bindClassBehaviour( + OnUpdatePropertiesPolicy.QNAME, + RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT, + new JavaBehaviour(this, "onUpdateProperties")); + } + + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + recordsManagementAuditService.auditEvent(nodeRef, name, before, after); + } + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuthenticatedUserRolesDataExtractor.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/AuthenticatedUserRolesDataExtractor.java similarity index 95% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuthenticatedUserRolesDataExtractor.java rename to rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/AuthenticatedUserRolesDataExtractor.java index e13489d476..e9729f158e 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/AuthenticatedUserRolesDataExtractor.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/AuthenticatedUserRolesDataExtractor.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.module.org_alfresco_module_rm.audit; +package org.alfresco.module.org_alfresco_module_rm.audit.extractor; import java.io.Serializable; import java.util.Set; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanIdentifierDataExtractor.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanIdentifierDataExtractor.java similarity index 94% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanIdentifierDataExtractor.java rename to rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanIdentifierDataExtractor.java index 28faaa9905..d760770c4b 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanIdentifierDataExtractor.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanIdentifierDataExtractor.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.module.org_alfresco_module_rm.audit; +package org.alfresco.module.org_alfresco_module_rm.audit.extractor; import java.io.Serializable; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNamePathDataExtractor.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNamePathDataExtractor.java similarity index 95% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNamePathDataExtractor.java rename to rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNamePathDataExtractor.java index 2b581e560e..1f8a97f3a1 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNamePathDataExtractor.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNamePathDataExtractor.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.module.org_alfresco_module_rm.audit; +package org.alfresco.module.org_alfresco_module_rm.audit.extractor; import java.io.Serializable; import java.util.List; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNodeRefPathDataExtractor.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNodeRefPathDataExtractor.java similarity index 94% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNodeRefPathDataExtractor.java rename to rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNodeRefPathDataExtractor.java index 25f07c10a4..121d1d0876 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/FilePlanNodeRefPathDataExtractor.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/audit/extractor/FilePlanNodeRefPathDataExtractor.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.module.org_alfresco_module_rm.audit; +package org.alfresco.module.org_alfresco_module_rm.audit.extractor; import java.io.Serializable; import java.util.List; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMActionProxyFactoryBean.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMActionProxyFactoryBean.java index 1f8a9b4b9a..3a7d74103d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMActionProxyFactoryBean.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMActionProxyFactoryBean.java @@ -25,15 +25,23 @@ import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.springframework.aop.framework.ProxyFactoryBean; +/** + * RM action proxy factory bean. + * + * @author Roy Wetherall + */ public class RMActionProxyFactoryBean extends ProxyFactoryBean { private static final long serialVersionUID = 539749542853266449L; + /** Runtime action service */ protected RuntimeActionService runtimeActionService; - private RecordsManagementActionService recordsManagementActionService; + /** Records management action service */ + protected RecordsManagementActionService recordsManagementActionService; - private RecordsManagementAuditService recordsManagementAuditService; + /** Records management audit service */ + protected RecordsManagementAuditService recordsManagementAuditService; /** * Set action service @@ -65,18 +73,20 @@ public class RMActionProxyFactoryBean extends ProxyFactoryBean this.recordsManagementAuditService = recordsManagementAuditService; } + /** + * Register the action + */ public void registerAction() { AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { public Void doWork() throws Exception { - // if (((RMActionExecuterAbstractBase)getTargetSource().getTarget()).isPublicAction() == true) - // { - // runtimeActionService.registerActionExecuter((ActionExecuter) getObject()); - // } - recordsManagementActionService.register((RecordsManagementAction) getObject()); - recordsManagementAuditService.register((RecordsManagementAction) getObject()); + RecordsManagementAction action = (RecordsManagementAction)getObject(); + + recordsManagementActionService.register(action); + // recordsManagementAuditService.registerActionAuditEvent(action); + return null; } }, AuthenticationUtil.getSystemUserName()); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java index b77da6e32d..816c491a8c 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/freeze/FreezeServiceImpl.java @@ -29,13 +29,15 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -44,6 +46,7 @@ import org.alfresco.service.cmr.dictionary.DictionaryService; 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.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; @@ -90,6 +93,12 @@ public class FreezeServiceImpl implements FreezeService, /** File Plan Service */ private FilePlanService filePlanService; + + /** Permission service */ + private PermissionService permissionService; + + /** file plan role service */ + private FilePlanRoleService filePlanRoleService; /** * @param policyComponent policy component @@ -138,14 +147,32 @@ public class FreezeServiceImpl implements FreezeService, { this.filePlanService = filePlanService; } + + /** + * @param permissionService permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param filePlanRoleService file plan role service + */ + public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService) + { + this.filePlanRoleService = filePlanRoleService; + } /** * Init service */ public void init() { - policyComponent.bindClassBehaviour(NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, this, new JavaBehaviour( - this, "beforeDeleteNode", NotificationFrequency.FIRST_EVENT)); + policyComponent.bindClassBehaviour( + NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, + this, + new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.FIRST_EVENT)); } /** @@ -270,8 +297,10 @@ public class FreezeServiceImpl implements FreezeService, boolean isRecord = recordService.isRecord(nodeRef); boolean isFolder = recordsManagementService.isRecordFolder(nodeRef); - if (!(isRecord || isFolder)) { throw new AlfrescoRuntimeException(I18NUtil - .getMessage(MSG_FREEZE_ONLY_RECORDS_FOLDERS)); } + if (!(isRecord || isFolder)) + { + throw new AlfrescoRuntimeException(I18NUtil.getMessage(MSG_FREEZE_ONLY_RECORDS_FOLDERS)); + } // Log a message about freezing the node with the reason if (logger.isDebugEnabled()) @@ -611,7 +640,12 @@ public class FreezeServiceImpl implements FreezeService, transferQName).append("'."); logger.debug(msg.toString()); } - + + // set inherit to false + permissionService.setInheritParentPermissions(holdNodeRef, false); + String allGroup = filePlanRoleService.getAllRolesContainerGroup(root); + permissionService.setPermission(holdNodeRef, allGroup, RMPermissionModel.FILING, true); + // Bind the hold node reference to the transaction AlfrescoTransactionSupport.bindResource(KEY_HOLD_NODEREF, holdNodeRef); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java index a6d3bc9086..9df1eb5564 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java @@ -187,7 +187,11 @@ public class FilePlanRoleServiceImpl implements FilePlanRoleService, public NodeRef doWork() { // Create "all" role group for root node - String allRoles = authorityService.createAuthority(AuthorityType.GROUP, getAllRolesGroupShortName(rmRootNode), "All Roles", new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); + String allRoles = authorityService.createAuthority( + AuthorityType.GROUP, + getAllRolesGroupShortName(rmRootNode), + "All Roles", + new HashSet(Arrays.asList(RMAuthority.ZONE_APP_RM))); // Set the permissions permissionService.setInheritParentPermissions(rmRootNode, false); @@ -195,10 +199,6 @@ public class FilePlanRoleServiceImpl implements FilePlanRoleService, permissionService.setPermission(rmRootNode, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS, true); permissionService.setPermission(rmRootNode, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING, true); - // set the capabilities - // permissionService.setPermission(rmRootNode, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.VIEW_RECORDS, true); - // permissionService.setPermission(rmRootNode, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.EDIT_NON_RECORD_METADATA, true); - // Create the unfiled record container return filePlanService.createUnfiledContainer(rmRootNode); } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/ListOfValuesGet.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/ListOfValuesGet.java index 6852b1a4cd..a12d465759 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/ListOfValuesGet.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/ListOfValuesGet.java @@ -28,8 +28,8 @@ import java.util.Set; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; -import org.alfresco.module.org_alfresco_module_rm.audit.AuditEvent; import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService; +import org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.disposition.property.DispositionProperty; import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent; diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordsManagementAuditServiceImplTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordsManagementAuditServiceImplTest.java index 2eac77a6f7..0b1e23138b 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordsManagementAuditServiceImplTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordsManagementAuditServiceImplTest.java @@ -26,10 +26,10 @@ import java.util.Locale; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.module.org_alfresco_module_rm.audit.AuditEvent; import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditEntry; import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditQueryParameters; import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService; +import org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; import org.alfresco.repo.security.authentication.AuthenticationException; diff --git a/rm-server/test/resources/test-context.xml b/rm-server/test/resources/test-context.xml index b1955bc387..9a1d6901ca 100644 --- a/rm-server/test/resources/test-context.xml +++ b/rm-server/test/resources/test-context.xml @@ -28,7 +28,9 @@ - + + + @@ -37,6 +39,7 @@ + @@ -44,7 +47,9 @@ - + + + @@ -53,6 +58,7 @@ + @@ -64,7 +70,8 @@ - + + RECORD @@ -77,7 +84,8 @@ - + + RECORD @@ -97,7 +105,8 @@ - + + RECORD