By Livern Chin
Snapshots
With the lack of snapshoting supports in alfresco share, we have implemented a snapshot approach to support change tracking to the share site as whole. This approach relies heavily on the versioning aspect of share and must be enabled in order for snapshoting to work successfully.
Snapshots are created as part of a business workflow process. They can be deployed to various file transfer receivers once successfully created.
Snapshots are implemented as a collection of specializedType content nodes called snapshotItem under a specializedType folder called snapshot in our custom site model. A snapshot folder will have custom properties to describe the snapshot itself such as id, label, snapshot state, its creator and others. A snapshot item is a content node that stores the nodeRef to the content node it represents and its current version in the source container.
We have also implemented a custom WebProject type which we represented as a specializedType folder called workarea. Our custom share site can have one or more workareas, which will have its own snapshots collections.
The process for creating a snapshot and snapshot revert are implemented as a java service in Alfresco. The createSnapshot method is expecting a site noderef and a workarea (“WebProject”) name the snapshot is to be created in, a label that describes the snapshot, and the state of the snapshot. In our implementation, the state is used to indicate if a snapshot is an approved snapshot for production deployment use or if it has been successfully deployed to production. With these provided parameters, a snapshot folder will be created in the specified site’s workarea with the proper label and state. Each noderef and version of the specified site’s workarea descendant will be used to create a snapshot item representing its source node and the version of its source node at the time this snapshot is created.
Here are snippets of the create snapshot method as mentioned above.
public NodeRef createSnapshot(NodeRef siteNodeRef, String sourceName, String label, String status) {
//determine the snapshots container, and the source container for the snapshot, based on the sourceName
final NodeRef snapshotContainerNodeRef = getSnapshotContainerNodeRef(siteNodeRef, sourceName);
NodeRef snapshotSourceNodeRef = null;
//lookup the snapshotSourceNodeRef by siteNodeRef and sourceName
snapshotSourceNodeRef = getSourceNodeRef(siteNodeRef, sourceName);
// collect all the nodes under source as a flat list.
final Snapshot snapshot = new Snapshot();
snapshot.setSnapshotLabel(label);
snapshot.setSnapshotStatus(status);
snapshot.setSnapshotId(String.valueOf(getNextSnapshotId(snapshotContainerNodeRef, true)));
String rootPath = Util.getRootPath(snapshotSourceNodeRef, nodeService, permissionService);
final List snapshotItems = collectItemsForSnapshot(snapshotSourceNodeRef, rootPath);
long snapshotCreateStart = 0L;
…. // save the snapshot to the repository.
Map snapshotFolderProps = snapshot.toNodeProperties();
String folderName = (String) snapshotFolderProps.get(ContentModel.PROP_NAME);
FileInfo snapshotFolder = fileFolderService.create(snapshotContainerNodeRef, folderName,
WebContentModel.TYPE_SNAPSHOT);
NodeRef snapshotNodeRef = snapshotFolder.getNodeRef();
nodeService.addProperties(snapshotNodeRef, snapshotFolderProps);
// the items in this snapshot are simply the items in the snapshotSourceNodeRef
for (SnapshotItem item : snapshotItems) {
// snapshotItem is a subtype of cm:content.
Map itemProps = item.toNodeProperties();
String itemName = (String) itemProps.get(ContentModel.PROP_NAME);
FileInfo repoItem = fileFolderService.create(snapshotNodeRef, itemName,
WebContentModel.TYPE_SNAPSHOT_ITEM);
nodeService.addProperties(repoItem.getNodeRef(), itemProps);
}
return snapshotNodeRef;
}
|
Snapshot Revert
The revert method will revert an entire target workarea specified in the method paramater targetContainerNodeRef to the state of the snapshot specified. In our implementation, we chose to first remove the target workarea, then restore each node from the version store by looping through the snapshot items registered in the snapshot and revert each node to its stored version property value.
// for each item:
for (SnapshotItem item : snapshotItems) {
String[] pathElements = StringUtils.split(item.getOrigPath(), '/');
NodeRef itemParent = ensureRelativeFolderPath(targetContainerNodeRef,
Arrays.asList(pathElements));
VersionHistory history = versionService.getVersionHistory(item.getItemNodeRef());
if (history == null) {
continue;
}
Version version = null;
try {
version = history.getVersion(item.getVersion());
} catch (VersionDoesNotExistException e) {
continue;
}
NodeRef versionStoreNodeRef = version.getFrozenStateNodeRef();
String fileName = item.getOrigName();
if (fileName == null) {
fileName = (String) nodeService.getProperty(versionStoreNodeRef, ContentModel.PROP_NAME);
}
QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, fileName);
if (nodeService.exists(item.getItemNodeRef())) {
logger.warn(item.getItemNodeRef() + " unexpectedly already exists.");
} else {
NodeRef restoredNode = versionService.restore(item.getItemNodeRef(), itemParent,
ContentModel.ASSOC_CONTAINS, assocQName, false);
revertNodeToSnapshotVersion(restoredNode, item);
nodeService.setProperty(restoredNode, ContentModel.PROP_NAME, fileName);
}
}
|
Deploying a snapshot
A snapshot can be used as a source for deployment. In order for us to deploy a snapshot, we have staging spaces allocated for each site workarea as need be to prepare content for deployment based on the snapshot items. Content nodes in the staging space are prepared by creating or updating the existing staging space node from the version store based on the snapshot item registered noderef and its stored version property. A deployed snapshot will be tracked in the target staging space to improve performance for future deployments.
private NodeRef createOrUpdateInStagingSpace(final NodeRef folderNodeRef, final SnapshotItem snapshotItem) {
NodeRef sourceNodeRef = snapshotItem.getItemNodeRef();
String desiredVersion = snapshotItem.getVersion();
String relativePath = snapshotItem.getOrigPath();
long versionLookupStartTime = 0L;
VersionHistory history = versionService.getVersionHistory(sourceNodeRef);
Version version = history.getVersion(desiredVersion);
NodeRef versionStoreNodeRef = version.getFrozenStateNodeRef();
ContentReader versionReader = contentService.getReader(versionStoreNodeRef, ContentModel.PROP_CONTENT);
if (versionReader == null || !versionReader.exists() || versionReader.getSize() == 0L) {
return null;
}
ContentData versionContentData = versionReader.getContentData();
return putContentInStagingSpace(folderNodeRef, relativePath, versionContentData, fileName);
}
|
Once the staging space is updated with the nodes represented in the snapshot items, it is ready for a file system transfer receiver deployment.
