/* * #%L * Alfresco Records Management Module * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * - * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * - * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.module.org_alfresco_module_rm.forms; import static org.alfresco.repo.security.authentication.AuthenticationUtil.runAsSystem; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.Set; import org.alfresco.model.ImapModel; import org.alfresco.module.org_alfresco_module_rm.compatibility.CompatibilityModel; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionScheduleImpl; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.dod5015.DOD5015Model; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanComponentKind; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.repo.forms.Field; import org.alfresco.repo.forms.FieldDefinition; import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.PropertyFieldDefinition; import org.alfresco.repo.forms.processor.node.FieldUtils; import org.alfresco.repo.forms.processor.node.FormFieldConstants; import org.alfresco.repo.i18n.StaticMessageLookup; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Implementation of a form processor Filter. * <p> * The filter ensures that any custom properties defined for the records * management type are provided as part of the Form and also assigned to the * same field group. * </p> * * @author Gavin Cornwell */ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter<NodeRef> implements RecordsManagementModel, DOD5015Model { /** Logger */ private static Log logger = LogFactory.getLog(RecordsManagementNodeFormFilter.class); protected static final String TRANSIENT_DECLARED = "rmDeclared"; protected static final String TRANSIENT_CATEGORY_ID = "rmCategoryIdentifier"; protected static final String TRANSIENT_DISPOSITION_INSTRUCTIONS = "rmDispositionInstructions"; /** Disposition service */ private DispositionService dispositionService; /** File Plan Service */ private FilePlanService filePlanService; /** * Returns the disposition service * * @return Disposition service */ protected DispositionService getDispositionService() { return this.dispositionService; } /** * Returns the file plan service * * @return File plan service */ protected FilePlanService getFilePlanService() { return this.filePlanService; } /** * Sets the disposition service * * @param dispositionService disposition service */ public void setDispositionService(DispositionService dispositionService) { this.dispositionService = dispositionService; } /** * @param filePlanService file plan service */ public void setFilePlanService(FilePlanService filePlanService) { this.filePlanService = filePlanService; } /** * @see org.alfresco.repo.forms.processor.Filter#afterGenerate(java.lang.Object, java.util.List, java.util.List, org.alfresco.repo.forms.Form, java.util.Map) */ @Override public void afterGenerate( NodeRef nodeRef, List<String> fields, List<String> forcedFields, Form form, Map<String, Object> context) { if (getFilePlanService().isFilePlanComponent(nodeRef)) { // add all the custom properties addCustomPropertyFieldsToGroup(form, nodeRef); FilePlanComponentKind kind = getFilePlanService().getFilePlanComponentKind(nodeRef); if (FilePlanComponentKind.RECORD.equals(kind)) { // add all the record meta-data aspect properties addRecordMetadataPropertyFieldsToGroup(form, nodeRef); // add required transient properties addTransientProperties(form, nodeRef); // add the supplemental marking list property forceSupplementalMarkingListProperty(form, nodeRef); // protect uneditable properties protectRecordProperties(form, nodeRef); // if the record is the result of an email we need to 'protect' some fields if (this.nodeService.hasAspect(nodeRef, ImapModel.ASPECT_IMAP_CONTENT)) { protectEmailExtractedFields(form, nodeRef); } } else if (FilePlanComponentKind.RECORD_FOLDER.equals(kind)) { // add the supplemental marking list property forceSupplementalMarkingListProperty(form, nodeRef); // add required transient properties addTransientProperties(form, nodeRef); } else if (FilePlanComponentKind.DISPOSITION_SCHEDULE.equals(kind)) { // use the same mechanism used to determine whether steps can be removed from the // schedule to determine whether the disposition level can be changed i.e. record // level or folder level. DispositionSchedule schedule = new DispositionScheduleImpl(this.rmServiceRegistry, this.nodeService, nodeRef); if (getDispositionService().hasDisposableItems(schedule)) { protectRecordLevelDispositionPropertyField(form); } } } } /** * * @param form * @param nodeRef */ protected void addCustomPropertyFieldsToGroup(Form form, NodeRef nodeRef) { Set<QName> customisables = rmAdminService.getCustomisable(nodeRef); // Compatibility support: don't show category properties if node of type series QName type = nodeService.getType(nodeRef); if (CompatibilityModel.TYPE_RECORD_SERIES.equals(type)) { // remove record category from the list of customisable types to apply to the form customisables.remove(TYPE_RECORD_CATEGORY); } for (QName customisable : customisables) { addPropertyFieldsToGroup(form, rmAdminService.getCustomPropertyDefinitions(customisable), CUSTOM_RM_FIELD_GROUP_ID, null); } } /** * * @param form * @param nodeRef */ protected void addRecordMetadataPropertyFieldsToGroup(Form form, NodeRef nodeRef) { Set<QName> aspects = recordService.getRecordMetadataAspects(nodeRef); for (QName aspect : aspects) { if (nodeService.hasAspect(nodeRef, aspect)) { String aspectName = aspect.getPrefixedQName(namespaceService).toPrefixString().replace(":", "-"); String setId = RM_METADATA_PREFIX + aspectName; String setLabel = null; AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect); if (aspectDefinition != null) { setLabel = aspectDefinition.getTitle(new StaticMessageLookup()); } addPropertyFieldsToGroup(form, dictionaryService.getPropertyDefs(aspect), setId, setLabel); } } } /** * Forces the "rmc:supplementalMarkingList" property to be present, if it is * already on the given node this method does nothing, otherwise a property * field definition is generated for the property. * * @param form The Form instance to add the property to * @param nodeRef The node the form is being generated for */ protected void forceSupplementalMarkingListProperty(Form form, NodeRef nodeRef) { if (!this.nodeService.hasAspect(nodeRef, RecordsManagementCustomModel.ASPECT_SUPPLEMENTAL_MARKING_LIST)) { PropertyDefinition propDef = this.dictionaryService.getProperty( RecordsManagementCustomModel.PROP_SUPPLEMENTAL_MARKING_LIST); if (propDef != null) { Field field = FieldUtils.makePropertyField(propDef, null, null, namespaceService, dictionaryService); form.addField(field); } else if (logger.isWarnEnabled()) { logger.warn("Could not add " + RecordsManagementCustomModel.PROP_SUPPLEMENTAL_MARKING_LIST.getLocalName() + " property as it's definition could not be found"); } } } /** * * @param form * @param nodeRef */ protected void addTransientProperties(final Form form, final NodeRef nodeRef) { if (recordService.isRecord(nodeRef)) { addTransientPropertyField(form, TRANSIENT_DECLARED, DataTypeDefinition.BOOLEAN, recordService.isDeclared(nodeRef)); } // Need to get the disposition schedule as the system user. See RM-1727. //Need to run all block as the system user, needed for disposition instructions, recordCategory and categoryId. See RM-3077. runAsSystem(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { DispositionSchedule ds = getDispositionService().getDispositionSchedule(nodeRef); if (ds != null) { String instructions = ds.getDispositionInstructions(); if (instructions != null) { addTransientPropertyField(form, TRANSIENT_DISPOSITION_INSTRUCTIONS, DataTypeDefinition.TEXT, instructions); } NodeRef recordCategory = getDispositionService().getAssociatedRecordsManagementContainer(ds); if (recordCategory != null) { String categoryId = (String) nodeService.getProperty(recordCategory, PROP_IDENTIFIER); if (categoryId != null) { addTransientPropertyField(form, TRANSIENT_CATEGORY_ID, DataTypeDefinition.TEXT, categoryId); } } } return null; } }); } /** * * @param form * @param name * @param type * @param value */ protected void addTransientPropertyField(Form form, String name, QName type, Object value) { String dataKeyName = FormFieldConstants.PROP_DATA_PREFIX + name; PropertyFieldDefinition declaredField = new PropertyFieldDefinition(name, type.getLocalName()); declaredField.setLabel(name); declaredField.setDescription(name); declaredField.setProtectedField(true); declaredField.setDataKeyName(dataKeyName); form.addFieldDefinition(declaredField); form.addData(dataKeyName, value); } /** * * @param form * @param nodeRef */ protected void protectRecordProperties(Form form, NodeRef nodeRef) { List<FieldDefinition> fieldDefs = form.getFieldDefinitions(); for (FieldDefinition fieldDef : fieldDefs) { if (!fieldDef.isProtectedField()) { String name = fieldDef.getName(); String prefixName = null; if ("size".equals(name) || "mimetype".equals(name) || "encoding".equals(name)) { prefixName = "cm:content"; } else { prefixName = fieldDef.getName(); } if (logger.isDebugEnabled()) { logger.debug("Checking property " + prefixName + " is editable by user " + AuthenticationUtil.getFullyAuthenticatedUser()); } QName qname = QName.createQName(prefixName, namespaceService); if (!recordService.isPropertyEditable(nodeRef, qname)) { if (logger.isDebugEnabled()) { logger.debug(" ... protected property"); } fieldDef.setProtectedField(true); } } } } /** * Marks all the fields that contain data extracted from an email * as protected fields. * * @param form The Form instance to add the property to * @param nodeRef The node the form is being generated for */ protected void protectEmailExtractedFields(Form form, NodeRef nodeRef) { // iterate round existing fields and set email fields as protected List<FieldDefinition> fieldDefs = form.getFieldDefinitions(); for (FieldDefinition fieldDef : fieldDefs) { String prefixName = fieldDef.getName(); // check the value of the property, if empty then do not mark property // as read only QName qname = QName.createQName(prefixName, namespaceService); Serializable value = nodeService.getProperty(nodeRef, qname); if (value != null && (prefixName.equals("cm:title") || prefixName.equals("cm:author") || prefixName.equals("dod:originator") || prefixName.equals("dod:publicationDate") || prefixName.equals("dod:dateReceived") || prefixName.equals("dod:address") || prefixName.equals("dod:otherAddress"))) { fieldDef.setProtectedField(true); } } if (logger.isDebugEnabled()) { logger.debug("Set email related fields to be protected"); } } /** * Marks the recordLevelDisposition property as protected to disable editing * * @param form The Form instance */ protected void protectRecordLevelDispositionPropertyField(Form form) { List<FieldDefinition> fieldDefs = form.getFieldDefinitions(); for (FieldDefinition fieldDef : fieldDefs) { if (fieldDef.getName().equals(RecordsManagementModel.PROP_RECORD_LEVEL_DISPOSITION.toPrefixString( this.namespaceService))) { fieldDef.setProtectedField(true); break; } } if (logger.isDebugEnabled()) { logger.debug("Set 'rma:recordLevelDisposition' field to be protected as record folders or records are present"); } } }