Monthly Archives: June 2013

ResourceSync and SWORD

resync_logoThis is the third post is a short series of blog posts about ResourceSync.  Thanks to Jisc funding, a small number of us from the UK have been involved in the NISO / OAI ResourceSync Initiative.  This has involved attending several meetings of the Technical Committee to help design the standard, working on documenting some of the different ResourceSync use cases, and working on some trial implementations.  As mentioned in the previous blog posts, I’ve been creating a PHP API library that makes it easy to interact with ResourceSync-enabled services.

In order to really test the library, it is good to think of a real end-to-end use case and implement it.  The use case I chose to do this was to mirror one repository to another, and to then keep it up to date.  This first involves a baseline sync to gather all the content, followed by an incremental sync of changes made each day.

ResourceSync provides the mechanism by which to gather the resources from the remote repository.  However another function is then required to take those resources and put them into the destination repository.  The obvious choice for this is SWORD v2.

ResourceSync is designed to list all files (or changed files) on a server.  These are then transferred using good old HTTP, but to get them into another repository requires a deposit protocol – in this case, SWORD.  In other words, ResourceSync is used to harvest the resources onto my computer, and SWORD is then used to deposit them into a destination repository.

The challenge here is linking resources together.  An ‘item’ in a repository is typically made up of a metadata resource, along with one or more associated file resources.  Because these are separate resources, they are listed independently in the ResourceSync resource lists.  However they contain attributes that link them together: ‘describes’ and ‘describedBy’.  The metadata ‘describes’ the file, and the file is ‘describedBy’ the metadata.  A good example of this is given in the CottageLabs description of how the OAI-PMH use case can be implemented using ResourceSync:

[xml]
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:rs="http://www.openarchives.org/rs/terms/">

<rs:ln rel="resourcesync" href="http://example.com/capabilitylist.xml"/>
<rs:md capability="resourcelist" modified="2013-01-03T09:00:00Z"/>

<url>
<loc>http://example.com/metadata-resource</loc>
<lastmod>2013-01-02T17:00:00Z</lastmod>
<rs:ln rel="describes" href="http://example.com/bitstream1"/>
<rs:ln rel="describedBy" href="http://purl.org/dc/terms/"/>
<rs:ln rel="collection" href="http://example.com/collection1"/>
<rs:md hash="md5:1584abdf8ebdc9802ac0c6a7402c03b6"
length="8876"
type="application/xml"/>
</url>

<url>
<loc>http://example.com/bitstream1</loc>
<lastmod>2013-01-02T17:00:00Z</lastmod>
<rs:ln rel="describedBy" href="http://example.com/metadata-resource"/>
<rs:ln rel="describedBy" href="http://example.com/other-metadata"/>
<rs:ln rel="collection" href="http://example.com/collection1"/>
<rs:md hash="md5:1e0d5cb8ef6ba40c99b14c0237be735e"
length="14599"
type="application/pdf"/>
</url>
</urlset>
[/xml]

So here’s the recipe (and here’s the code) for syncing a resource list such as this, and then depositing it into a remote repository using SWORD.  Both use PHP libraries, which makes the code quite short.

The recipe

[php]
include_once(‘../../ResyncResourcelist.php’);
$resourcelist = new ResyncResourcelist(‘http://93.93.131.168:8080/rs/resourcelist.xml’);
$resourcelist->registerCallback(function($file, $resyncurl) {
// Work out if this is a metadata object or a file
global $metadataitems, $objectitems;
$type = ‘metadata’;
$namespaces = $resyncurl->getXML()->getNameSpaces(true);
if (!isset($namespaces[‘sm’])) $sac_ns[‘sm’] = ‘http://www.sitemaps.org/schemas/sitemap/0.9’;
$lns = $resyncurl->getXML()->children($namespaces[‘rs’])->ln;
$key = ”;
$owner = ”;
foreach($lns as $ln) {
if (($ln->attributes()->rel == ‘describedby’) && ($ln->attributes()->href != ‘http://purl.org/dc/terms/’)) {
$type = ‘object’;
$key = $resyncurl->getLoc();
$owner = $ln->attributes()->href;
}
}

echo ‘ – New file saved: ‘ .$file . "\n";
echo ‘  – Type: ‘ . $type . "\n";

if ($type == ‘metadata’) {
$metadataitems[] = $resyncurl;
} else {
$objectitems[(string)$key] = $resyncurl;
$resyncurl->setOwner($owner);
}
});
[/php]

This piece of code is performing a baseline sync, and is using the callback registration option mentioned in the last blog.  The callback is just doing one thing: sorting the metadata objects into one list, and the file objects into another.  These will then be processed later.

Next, each metadata item is processed in order to deposit that metadata object into the destination repository using SWORD v2:

[php]
foreach ($metadataitems as $item) {
echo " – Item " . ++$counter . ‘ of ‘ . count($metadataitems) . "\n";
echo "  – Metadata file: " . $item->getFileOnDisk() . "\n";
$namespaces = $xml->getNameSpaces(true);
if (!isset($namespaces[‘dc’])) $sac_ns[‘dc’] = ‘http://purl.org/dc/terms/’;
if (!isset($namespaces[‘dcterms’])) $sac_ns[‘dc’] = ‘http://purl.org/dc/elements/1.1/’;
$dc = $xml->children($namespaces[‘dc’]);
$dcterms = $xml->children($namespaces[‘dcterms’]);
$title = $dc->title[0];
$contributor = $dc->contributor[0];
$id = $dc->identifier[0];
$date = $dcterms->issued[0];
echo ‘   – Location: ‘ . $item->getLoc() . "\n";
echo ‘   – Author: ‘ . $contributor . "\n";
echo ‘   – Title: ‘ . $title . "\n";
echo ‘   – Identifier: ‘ . $id . "\n";
echo ‘   – Date: ‘ . $date . "\n";

// Create the atom entry
$test_dirin = ‘atom_multipart’;
$atom = new PackagerAtomTwoStep($resync_test_savedir, $sword_deposit_temp, ”, ”);
$atom->setTitle($title);
$atom->addMetadata(‘creator’, $contributor);
$atom->setIdentifier($id);
$atom->setUpdated($date);
$atom->create();

// Deposit the metadata record
$atomfilename = $resync_test_savedir . ‘/’ . $sword_deposit_temp . ‘/atom’;
echo ‘  – About to deposit metadata: ‘ . $atomfilename . "\n";
$deposit = $sword->depositAtomEntry($sac_deposit_location,
$sac_deposit_username,
$sac_deposit_password,
”,
$atomfilename,
true);
[/php]

This option being used here is to first create an atom entry that contains the metadata, and depositing that.  The SWORD v2 ‘in-progress’ flag is being set to TRUE, which indicates that further activity will take place to the record.

The code then needs to look through the list of file resources, and find any that are ‘describedBy’ the metadata record in question.  Any that are, are deposited to the same record using SWORD v2:

[php]
// Find related files for this metadata record
foreach($objectitems as $object) {
if ((string)$object->getOwner() == (string)$item->getLoc()) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $object->getFileOnDisk());
echo ‘    – Related object: ‘ . $object->getLoc() . "\n";
echo ‘     – File: ‘ . $object->getFileOnDisk() . ‘ (‘ . $mime . ")\n";

// Deposit file
$deposit = $sword->addExtraFileToMediaResource($edit_media,
$sac_deposit_username,
$sac_deposit_password,
”,
$object->getFileOnDisk(),
$mime);
}
}
[/php]

Using the SWORD v2 API library is very easy: once you have the file and its MIME type, it is a single line of code to add that file to the record in the destination repository.

Once all the related files have been added, the final step is to set the ‘in-progress’ flag to FALSE to indicate that the object is complete, and that it can be formally archived into the repository.  This is a simple as:

[php]
// Complete the deposit
$deposit = $sword->completeIncompleteDeposit($edit_iri,
$sac_deposit_username,
$sac_deposit_password,
”);
[/php]

The end to end process has now taken place – the items have been harvested using ResourceSync, and then deposited back using SWORD v2.

Limitations

The default DSpace implementation of the SWORD v2 protocol allows items to deposited, updated, and deleted.  It does this by keeping items in the workflow, and when the ‘In-progress’ flag is set to false, the deposit is completed by moving it out of the workflow and into the main archive.  Once the item is moved into the main archive, it can no longer be edited using SWORD.

This is a sensible approach for most situations.  Once an item has been formally ingested, it is under the control of the archive manager, and the original depositor should probably not have the rights to make further changes.

However in the case of performing a synchronisation with ResrcoueSync, the master copy of the data is in a remote repository, and that should therefore be allowed to overwrite data that is formally archived in the repository.  This is an implementation option though, and if an alternative WorkflowManager was written, this could be changed.

[Update: 20th June 2013.  I have now edited the default WorkflowManager, to make one that permits updates to items that are in workflow or in the archive.  This overcomes this limitation.  I hope to add this as a configurable option to a future release of DSpace.]

Conclusion

ResourceSync and SWORD are two complementary interoperability protocols. ResourceSync can be used to harvest all content from one site, and SWORD used to deposit that content into another.

ResourceSync can differentiate between new, updated, and deleted content.  SWORD v2 also allows these interactions, so can be used to reflect those changes as they happen.как разместить объявление в контакте

Resourcesync: Making things happen with callbacks

resync_logoIn a previous blog post I introduced the ResourceSync PHP API library.  This is a code library written PHP that makes it easy to interact with web sites that support the new ResourceSync standard.  The default behavior for the code when scynchronising with a server either during a baseline sync (complete sync) or a incremental sync (of only changed files since the last baseline sync) is to simply download the files and store them on disk in the same directories as they exist on the server.

However, unless you want to just store the files for backup purposes, the chances are that you’ll want to process them in some way.  There are two ways to do this, either perform the synchronisation, and then process the files, or process them as they are downloaded.

From the last post, you’ll know that by using the ResourceSync PHP library, performing a sync can be as simple as:

[php]
include ‘ResyncResourcelist.php’;
$resourcelist = new ResyncResourcelist(‘http://example.com/resourcelist.xml’);
$resourcelist->baseline(‘/resync’);
[/php]

This will process the resourcelist file by file, and download them to the /resync/ directory.

In order to process these, you need to register a ‘callback’ function with the library.  Each time an item is synchronised, the code in the callback function will be executed.

The following code snippet shows a very simple example of a callback.  This example displays the filename of the resource that has been downloaded, and prints the XML that described the file in the ResourceSync resourcelist.  The XML can be useful as it provides contextual information about the file, such as its size, checksum, last modified date, and links to related items.  Of course some of these will have already been checked by the library (such as last modified date when using the date range option, and the checksum to make sure the file has been retried successfully).

[php]
$resourcelist->registerCallback(function($file, $resyncurl) {
echo ‘  – Callback given value of ‘ .$file . "\n";
echo ‘   – XML:’ . "\n" . $resyncurl->getXML()->asXML() . "\n";
});
[/php]

When performing a baseline sync using the ResyncResourcelist class it is only possible to register a single callback.  This is called whenever any file is downloaded.

However the ResyncChangelist class allows three different callbacks to be registered, depending on the action: CREATED, UPDATED, or DELETED.

[php]
$changelist->registerCreateCallback(function($file, $resyncurl) {
echo ‘  – CREATE Callback given value of ‘ .$file . "\n";
echo ‘   – XML:’ . "\n" . $resyncurl->getXML()->asXML() . "\n";
});

$changelist->registerUpdateCallback(function($file, $resyncurl) {
echo ‘  – UPDATE Callback given value of ‘ .$file . "\n";
echo ‘   – XML:’ . "\n" . $resyncurl->getXML()->asXML() . "\n";
});

$changelist->registerDeleteCallback(function($file, $resyncurl) {
echo ‘  – DELETE Callback given value of ‘ .$file . "\n";
echo ‘   – XML:’ . "\n" . $resyncurl->getXML()->asXML() . "\n";
});
[/php]

Depending on the purpose of your code, it is likely that you would want to handle these three types of events in different ways, hence the three callback options.

In the next blog post, I’ll show an example of this code in action, as it uses the callback to look at each resource’s XML to discover whether it is a metadata file or a related resource.  It then uses this information to deposit the item into a repository using SWORD.разработка и поддержка web сайтов

The ResourceSync PHP Library

resync_logoOver the past year, thanks to funding from the Jisc, I’ve been involved with the NISO / OAI ResourceSync initiative.  The aim of ResourceSync is to provide mechanisms for large-scale synchronisations of web resources.  There are lots of use cases for this, and many reasons why it is an interesting problem.  For some background reading, I’d suggest:

The specification itself can be read at http://www.openarchives.org/rs, and a quick read will highlight very quickly that the specification is based on sitemaps (http://www.sitemaps.org/) which is no surprise, given that they were developed for the easy and efficient listing of web resources for search engine crawlers to harvest – which in itself is a specialised form of resource synchronisation.

As with anything new, the proof is always in the pudding, which in this context means that reference implementations are required in order to both test that a standard can be implemented and fulfill the original use cases it was designed to do, but also to smooth off any rough edges that only appear once you use it in anger.

My role therefore has been to develop a PHP ResourceSync client library.  The role of a client library is to allow other software systems to easily interact with a technology – in this case, web servers that support ResourceSync.  The client library therefore provides the facility to connect to a web server and synchronise the contents, and then to stay up to date by loading lists of resources that have been created, updated, or deleted.

The PHP library can be downloaded from: https://github.com/stuartlewis/resync-php

The rest of this blog post will step through the different parts of ResourceSync, and shows how they can be access by the PHP client library:

The first step is to discover whether a site supports ResourceSync.  The mechanism to do this is by using the well-known URI specification (see: RFC5785).  Put simply, if a server supports ResourceSync, it places a file at http://www.example.com/.well-known/resourcesync which then points to where the capability list exists.

The first function of the PHP ResourceSync library is therefore to support this discovery:

[php]
include(‘ResyncDiscover.php’);
$resyncdiscover = new ResyncDiscover(‘http://example.com/’);
$capabilitylists = $resyncdiscover->getCapabilities();
echo ‘ – There were ‘ . count($capabilitylists) .
‘ capability lists found:’ . "\n";
foreach ($capabilitylists as $capabilties) {
echo ‘ – ‘ . $capabilties . "\n";
}
[/php]

Zero, one, or more capability list URIs are returned.  If none are returned, then the site doesn’t support ResourceSync.  If one is returned, the next step is to examine the capability list to see which parts of the ResourceSync protocol are supported:

[php]
include(‘ResyncCapabilities.php’);
$resynccapabilities = new ResyncCapabilities(‘http://example.com/capabilitylist.xml’);
$capabilities = $resynccapabilities->getCapabilities();
echo ‘Capabilities’ . "\n";
foreach($capabilities as $capability => $type) {
echo ‘ – ‘ . $capability . ‘ (capability type: ‘ . $type . ‘)’ . "\n";
}
[/php]

The output of this is that the specific ResourceSync capabilities supported by that server will be returned.  Typically a resourcelist and a changelist will be shown.

The next step is often to perform a baseline sync (complete download of all resources).  Again, the PHP library supports this:

[php]
include ‘ResyncResourcelist.php’;
$resourcelist = new ResyncResourcelist(‘http://example.com/resourcelist.xml’);
$resourcelist->enableDebug(); // Show progress
$resourcelist->baseline(‘/resync’);
[/php]

It is possible to ask the library how many files it has downloaded, and how large they were:

[php]
echo $resourcelist->getDownloadedFileCount() . ‘ files downloaded, and ‘ .
$resourcelist->getSkippedFileCount() . ‘ files skipped’ . "\n";
echo $resourcelist->getDownloadSize() . ‘Kb downloaded in ‘ .
$resourcelist->getDownloadDuration() . ‘ seconds (‘ .
($resourcelist->getDownloadSize() /
$resourcelist->getDownloadDuration()) . ‘ Kb/s)’ . "\n";
[/php]

It is possible to also restrict the files to be downloaded to those from a certain date.  This can be useful if you only want to synchronise recently created files:

[php]
$from = new DateTime("2013-05-18 00:00:00.000000");
$resourcelist->baseline(‘/resync’, $from);
[/php]

Once a baseline sync has taken place, all of the files exposed via the ResourceSync interface will now exist on the local computer.  The next step is to routinely keep this set of resources up to date.  To do this, depending on the frequency at which the server produces change lists, these should be processed to download new or updated files, and to delete old files:

[php]
include ‘ResyncChangelist.php’;
$changelist = new ResyncChangelist(‘http://example.com/changelist.xml’);
$changelist->enableDebug(); // Show progress
$changelist->process(‘/resync’);
[/php]

Again, there are options to see what files have been processed:

[php]
echo ‘ – ‘ . $changelist->getCreatedCount() . ‘ files created’ . "\n";
echo ‘ – ‘ . $changelist->getUpdatedCount() . ‘ files updated’ . "\n";
echo ‘ – ‘ . $changelist-getDeletedCount() . ‘ files deleted’ . "\n";
echo $changelist->getDownloadedFileCount() . ‘ files downloaded, and ‘ .
$changelist->getSkippedFileCount() . ‘ files skipped’ . "\n";
echo $changelist->getDownloadSize() . ‘Kb downloaded in ‘ .
$changelist->getDownloadDuration() . ‘ seconds (‘ .
($changelist->getDownloadSize() /
$changelist->getDownloadDuration()) . ‘ Kb/s)’ . "\n";
[/php]

Also again, it is possible to only see changes since a particular date.  This can be used to keep note of when the sync was last attempted, meaning only changes made since then are processed:

[php]
$from = new DateTime("2013-05-18 00:00:00.000000");
$changelist->process(‘/resync’, $from);
[/php]

The PHP library allows in a few steps, each consisting of a few lines, for the contents of a ResourceSync enabled server to be kept in sync with a local copy.

A further two blog posts will be published in this series.  The next will show how to interact with the library so that more complex actions can be performed when resources are created, updated, or deleted.  The final blog post will show this in action, with an application of the PHP ResourceSync library making use of the resources it processes.как разместить контекстную рекламу