AnsweredAssumed Answered

Records Management AMP creates records from temporary files

Question asked by johna1 on Jun 24, 2009
We are using
•   Alfresco Enterprise 3.1 Trial Version
•   alfresco-recordsmanagement-2.1.0.amp

We experience the following problems in file plan folders:
•   Records are created for temporary files each time a word/excel document is opened or saved.
•   Records are created for temporary files each time a file is checked out
•   This results in the record ids not being sequential (because each temporary file record uses the next record ID and then the file is deleted)
•   Opening a word .doc document in Word 2007 takes about 30 seconds and causes server-side error logs when trying to extract metadata from the temporary "~$*.doc" file.

To address these issues we have modified:
alfresco/module/org.alfresco.module.RecordsManagement/script/ onCreateChildAssociation_rmaFilePlan_cmContains.js

The problem seems to be that the onCreateChildAssociation_rmaFilePlan_cmContains.js is running before the sys:temporary aspect is being set on the file.

We also added code to change the owner of non-temporary files to be "admin" so that users with collaborator permissions cannot delete records they create.
Without this, any record and all versions can be deleted by the user that created the record in Alfresco.

TODO:
Improvements to the code change should include:
•   Use the tempFileMarkerInterceptor or similar bean to determine if the file is a temporary one, see classes/alfresco/model-specific-services-context.xml. Note that the code we added, also checks for filenames that match a nodeId GUID pattern. Temporary files with a GUID filename are created when a file is checked out.
•   Add a file plan property to control whether the owner of new nodes (files and folders) is changed.

Modified onCreateChildAssociation_rmaFilePlan_cmContains.js:
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program 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 General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*
* Script: onCreateChildAssociation_rmaFilePlan_cmContains.js
* Author: Roy Wetherall
*
* Behaviour script executed when a contains assoc is created under a file plan.
*/

/**
* Calculates the name of the record based on the counter and information
* retrieved from the passed file plan.
*
* @param record    the record
* @param filePlan  the file plan
*/
function calculateRecordName(record, filePlan)
{
    // Get the category id
    var catid = filePlan.properties["rma:recordCategoryIdentifier"];
    // TODO test that the cat has been set ..
      
    // Get the current record count
    var countAction = actions.create("counter");
    countAction.execute(filePlan);
    var recordCounter = filePlan.properties["cm:counter"];
      
    // Pad to get the db id
    var dbid = utils.pad(String(recordCounter), 4);
    logger.log(record.name + ": " + "catid = " + catid + " and dbid = " + dbid);
        
    // Set the property values based on the parent file plan
    record.properties["rma:recordIdentifier"] = catid + "-" + dbid;
    if (record.isContainer == true)
    {
        record.properties["cm:name"] = "Folder " + dbid + " - " + record.properties["cm:name"];
    }
    else
    {
        record.properties["cm:name"] = catid + "-" + dbid + " " + record.properties["cm:name"];
    } 
   
    record.save(); 
}

/**
* Initialise the property values of the record based on the passed file plan
*
* @param record    the record
* @param filePlan  the file plan
*/
function initialiseRecordProperties(record, filePlan)
{
    // Get the title and description of the record
    var title = record.properties["cm:title"];
    var description = record.properties["cm:description"];

    // Set the property values of the record based on the file plan
    record.properties["cm:title"] = record.properties["cm:name"];
    if (description == "" || description == null)
    {
        description = title;
    }
    record.properties["rma:subject"] = description;
    record.properties["rma:originatingOrganization"] = filePlan.properties["rma:defaultOriginatingOrganization"];
    record.properties["rma:recordNote"]              = filePlan.properties["rma:filePlanNote"];
    record.properties["rma:supplementalMarkingList"] = filePlan.properties["rma:defaultMarkingList"];
    record.properties["rma:mediaFormat"]             = filePlan.properties["rma:defaultMediaType"];
    record.properties["rma:originatingOrganization"] = filePlan.properties["rma:defaultOriginatingOrganization"];
    record.properties["cm:description"]              = filePlan.properties["rma:dispositionInstructions"];
    record.properties["rma:privacyActSystem"] = filePlan.properties["rma:privacyActSystem"];
    if (record.isContent == true)
    {
        record.properties["rma:format"] = record.mimetype;  
    }
    record.properties["rma:dateFiled"]          = record.properties["cm:modified"];
    record.properties["rma:publicationDate"]    = record.properties["cm:modified"];  
   
    if (record.properties["cm:author"] == null || record.properties["cm:author"] == "")
    {
        record.properties["rma:originator"] = record.properties["cm:creator"];
        record.properties["rma:addressee"] = record.properties["cm:creator"];
    }
    else
    {
        record.properties["rma:originator"] = record.properties["cm:author"];
        record.properties["rma:addressee"] = record.properties["cm:author"];
    }

    record.properties["rma:dateReceived"] = record.properties["cm:modified"].getTime();

    record.save();
}

function applyLifeCycleAspects(record, fileplan)
{
    // Add the vital record apsect if required
    if (filePlan.properties[rm.PROP_VITAL_RECORD_INDICATOR] == true)
    {
        record.addAspect("rma:vitalrecord");
    }
   
    // Add the process cuttof aspect if required
    if (filePlan.properties["rma:processCutoff"] == true)
    {
        record.addAspect("rma:cutoffSchedule");
    }    
}

// Get the file plan and record nodes
var filePlan = behaviour.args[0].parent;
var record = behaviour.args[0].child;

//function trimNS(s){
//   // Remove namespace prefixes (inside {}) from Qname ids in string
//   return s.replace(/\{[^}]*\}|Node /g, "");
//}

logger.log("Starting onCreateChildAssociation_rmaFilePlan_cmContains.js: "
      + record.name + "(" + record.id + ")"
      + " " + record); // Record returns node type and aspects
logger.log("In filePlan " + filePlan.name + " " + filePlan);

// Log the name, nodeId, type and aspects of all files in the filePlan folder
var i=0;
for each (n in filePlan.children)
{
   i++;
   logger.log("Child " + i + ": " + n.name + " " + n.id + " " + n);
}

/*
Check for temporary file names
Without these checks:
- records are created each time a file is opened, saved or checked out
- opening a file takes about 30 seconds and causes server-side errors from trying to extract metadata from the Word/Excel temporary file.

Note: Word File/SaveAs does not result in a filename according to the filePlan:
   The filePlan filename is created, but I assume word then renames the FilePlan file back to the original SaveAs name
   The Record Data / Unique Record Identifier is set correctly.

Also set the owner of non-temporary files to be "admin" so that collaborators cannot delete records they create.

Word 2007 and Excel 2007 File/Save triggers this script 4 times with the following filenames and NodeID-GUIDs:
1. TempName1.TMP, NodeId1
2. CurrentDoc name, NodeId2 (the id the document had before editing)
3. TempName2.TMP, NodeId2
4. TempName1.TMP, NodeId1
This script is called 6 times for Word/Excel 2007 File/SaveAs.
*/

// Check for filenames that look like node ids (e.g. a4876ff3-ebd7-416c-b0c7-f7f6bb54241c)
// These get created when a file is checked out and when a document is moved (document.move()) into the space.
var nodeIdRegex="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
var temporaryFile =
   record.name.indexOf("~") == 0
   || record.name.match(" \(Working Copy\)\.") != null
   || record.name.match(nodeIdRegex) != null
   || record.name.match("\.tmp$") != null
   ;

// Logging
logger.log("Filename: " + record.name
      + " temporaryFile=" + temporaryFile
      + " " + record.name.indexOf("~")
      + " " + record.name.match(" \(Working Copy\)\.")
      + " " + record.name.match(nodeIdRegex)
      + " " + record.name.match("\.tmp$")
      );

if ( !temporaryFile ) {
   // Change owner of normal files and folders to be admin so that collaborators cannot delete records they create.
   // But don't change the owner of files that are already records - this occurs during the 4 times
   //   onCreateChildAssociation_rmaFilePlan_cmContains.js is called when word/excel 2007 saves a file.
   // Changing the owner of all non-temporary files causes word/excel 2007 file/save to fail.
    if (record.hasAspect("rma:record") == false) {
       logger.log("Changing owner to admin for: " + record.name + "(" + record.id + ")" );
       try {
          record.setOwner("admin");
          record.save();
       } catch (err){
          logger.log("Change owner to admin failed: " + err.description );
       }
   }
} else {
   if (record.hasAspect("sys:temporary") == true ) {
      logger.log("Temporary aspect already set for: " + record.name);
   } else {
      logger.log("Setting temporary aspect for: " + record.name);
      // Setting the temporary aspect seems optional from testing so far.
      // Probably more correct to set it.
      record.addAspect("sys:temporary");
      record.save();
   }
}

// The check for !temporaryFile was added when testing whether we should set the temporary aspect above
// if (record.hasAspect("sys:temporary") == false && !temporaryFile)
if (record.hasAspect("sys:temporary") == false)
{  
    // Add the record aspect if it has not already been added
    if (record.hasAspect("rma:record") == false)
    {
        logger.log("Adding rma:record aspect");
        record.addAspect("rma:record");
       
        // Link to the parent file plan
        rm.linkToFilePlan(record, filePlan);
    }
   
    // Calcuate record name
    logger.log("Calculating record name");
    calculateRecordName(record, filePlan);
   
    // Initialise the records property values
    logger.log("Initialising record properties");
    initialiseRecordProperties(record, filePlan);
   
    // Add the required lifecycle aspects
    logger.log("Applying life cycle aspects");
    applyLifeCycleAspects(record, filePlan);
   
    // Save any changes made to the record
    record.save();  
}

Outcomes