001/*
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.migration.handlers;
017
018import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
019import com.hp.hpl.jena.graph.Node;
020import com.hp.hpl.jena.graph.NodeFactory;
021import com.hp.hpl.jena.graph.Triple;
022import com.hp.hpl.jena.rdf.model.Model;
023import com.hp.hpl.jena.rdf.model.ModelFactory;
024import com.hp.hpl.jena.rdf.model.Statement;
025import com.hp.hpl.jena.rdf.model.StmtIterator;
026import com.hp.hpl.jena.sparql.modify.request.QuadAcc;
027import com.hp.hpl.jena.sparql.modify.request.QuadDataAcc;
028import com.hp.hpl.jena.sparql.modify.request.UpdateDataInsert;
029import com.hp.hpl.jena.sparql.modify.request.UpdateDeleteWhere;
030import com.hp.hpl.jena.update.UpdateFactory;
031import com.hp.hpl.jena.update.UpdateRequest;
032import org.apache.jena.atlas.io.IndentedWriter;
033import org.apache.jena.riot.system.ErrorHandlerFactory;
034import org.fcrepo.migration.DatastreamVersion;
035import org.fcrepo.migration.ExternalContentURLMapper;
036import org.fcrepo.migration.Fedora4Client;
037import org.fcrepo.migration.FedoraObjectVersionHandler;
038import org.fcrepo.migration.MigrationIDMapper;
039import org.fcrepo.migration.ObjectProperty;
040import org.fcrepo.migration.ObjectReference;
041import org.fcrepo.migration.ObjectVersionReference;
042import org.fcrepo.migration.foxml.DC;
043import org.fcrepo.migration.foxml.NamespacePrefixMapper;
044import org.fcrepo.migration.urlmappers.SelfReferencingURLMapper;
045import org.slf4j.Logger;
046
047import javax.xml.bind.JAXBException;
048import javax.xml.datatype.DatatypeConfigurationException;
049import javax.xml.datatype.DatatypeFactory;
050import javax.xml.datatype.XMLGregorianCalendar;
051import java.io.ByteArrayOutputStream;
052import java.io.File;
053import java.io.FileInputStream;
054import java.io.FileNotFoundException;
055import java.io.IOException;
056import java.util.ArrayList;
057import java.util.Date;
058import java.util.GregorianCalendar;
059import java.util.List;
060import java.util.Properties;
061
062import static org.slf4j.LoggerFactory.getLogger;
063
064/**
065 *
066 * @author mdurbin
067 *
068 */
069public class BasicObjectVersionHandler implements FedoraObjectVersionHandler {
070
071    private static Logger LOGGER = getLogger(BasicObjectVersionHandler.class);
072
073    private static int suffix = 0;
074
075    private Fedora4Client f4client;
076
077    private MigrationIDMapper idMapper;
078
079    private boolean importExternal;
080
081    private boolean importRedirect;
082
083    private ExternalContentURLMapper externalContentUrlMapper;
084
085    private NamespacePrefixMapper namespacePrefixMapper;
086
087    private boolean skipDisseminators = false;
088
089    private Properties customPropertyMapping;
090
091    /**
092     * Basic object version handler.
093     * @param client a fedora4 client
094     * @param idMapper the id mapper
095     * @param localFedoraServer uri to fedora server
096     * @param namespacePrefixMapper a namespace prefix mapper.
097     */
098    public BasicObjectVersionHandler(final Fedora4Client client,
099                                     final MigrationIDMapper idMapper,
100                                     final String localFedoraServer,
101                                     final NamespacePrefixMapper namespacePrefixMapper) {
102        this.f4client = client;
103        this.idMapper = idMapper;
104        this.externalContentUrlMapper = new SelfReferencingURLMapper(localFedoraServer, idMapper);
105        this.namespacePrefixMapper = namespacePrefixMapper;
106
107        // Do not throw exception for invalid RDF
108        ErrorHandlerFactory.setDefaultErrorHandler(ErrorHandlerFactory.errorHandlerWarn);
109    }
110
111    /**
112     * A property setter for a property that determines the handling of External (X)
113     * fedora 3 datastreams.  If true, the content of the URL to which those datastreams
114     * redirect is fetched and ingested as a fedora 4-managed non-RDF resource.  If false
115     * (default), a non-RDF resource is created in fedora 4 that when fetched results in
116     * an HTTP redirect to the external url.
117     *
118     * @param value indicating if content is external
119     */
120    public void setImportExternal(final boolean value) {
121        this.importExternal = value;
122    }
123
124    /**
125     * A property setter for a property that determines the handling of Redirect (R)
126     * fedora 3 datastreams.  If true, the content of the URL to which those datastreams
127     * redirect is fetched and ingested as a fedora 4-managed non-RDF resource.  If false
128     * (default), a non-RDF resource is created in fedora 4 that when fetched results in
129     * an HTTP redirect to the external url.
130     *
131     * @param value indicating if content is imported
132     */
133    public void setImportRedirect(final boolean value) {
134        this.importRedirect = value;
135    }
136
137    /**
138     * A property setter for the optional propertly that indicates a Properties file whose
139     * key value pairs represent custom mappings from fedora 3 properties to fedora 4
140     * properties.
141     * @param propertiesFile a properties file containing mappings from foxml to fedoar 4 properties
142     */
143    public void setCustomPropertyMapping(final File propertiesFile) {
144        this.customPropertyMapping = new Properties();
145        try {
146            final FileInputStream fis = new FileInputStream(propertiesFile);
147            try {
148                this.customPropertyMapping.load(fis);
149            } finally {
150                fis.close();
151            }
152        } catch (FileNotFoundException e) {
153            throw new RuntimeException("The file, \"" + propertiesFile.getAbsolutePath()
154                    + "\" , specified in the 'customPropertyMapping' property was not found!", e);
155        } catch (IOException e) {
156            throw new RuntimeException("Error access the file, \"" + propertiesFile.getAbsolutePath()
157                    + "\" , specified in the 'customPropertyMapping' property!", e);
158        }
159    }
160
161    /**
162     * Sets a property that indicates whether fedora 2 disseminators will be skipped or
163     * not.
164     * @param skip indicates whether it is ok to skip fedora 2 disseminators.
165     */
166    public void setSkipDisseminators(final boolean skip) {
167        this.skipDisseminators = skip;
168    }
169
170    @Override
171    public void processObjectVersions(final Iterable<ObjectVersionReference> versions) {
172        String objectPath = null;
173        try {
174            for (final ObjectVersionReference version : versions) {
175
176                LOGGER.debug("Considering object "
177                        + version.getObjectInfo().getPid()
178                        + " version at " + version.getVersionDate() + ".");
179
180                if (objectPath == null) {
181                    if (!skipDisseminators && version.getObject().hadFedora2Disseminators()) {
182                        throw new RuntimeException("Fedora 2 disseminators are not supported, set" +
183                                " \"skipDisseminators\" to true to migration this object without" +
184                                " the disseminators!");
185                    }
186                    objectPath = idMapper.mapObjectPath(version.getObjectInfo().getPid());
187                    if (!f4client.exists(objectPath)) {
188                        f4client.createPlaceholder(objectPath);
189                    } else if (!f4client.isPlaceholder(objectPath)) {
190                        LOGGER.warn(objectPath + " already exists, skipping migration of "
191                                + version.getObject().getObjectInfo().getPid() + "!");
192                        return;
193                    }
194                }
195
196                final QuadDataAcc triplesToInsert = new QuadDataAcc();
197                final QuadAcc triplesToRemove = new QuadAcc();
198
199                for (final DatastreamVersion v : withRELSINTLast(version.listChangedDatastreams())) {
200                    LOGGER.debug("Considering changed datastream version " + v.getVersionId());
201                    final String dsPath = idMapper.mapDatastreamPath(v.getDatastreamInfo().getObjectInfo().getPid(),
202                            v.getDatastreamInfo().getDatastreamId());
203                    if (v.getDatastreamInfo().getDatastreamId().equals("DC")) {
204                        migrateDc(v, triplesToRemove, triplesToInsert);
205                    } else if (v.getDatastreamInfo().getDatastreamId().equals("RELS-EXT")) {
206                        migrateRelsExt(v, triplesToRemove, triplesToInsert);
207                    } else if (v.getDatastreamInfo().getDatastreamId().equals("RELS-INT")) {
208                        migrateRelsInt(v);
209                    } else if ((v.getDatastreamInfo().getControlGroup().equals("E") && !importExternal)
210                            || (v.getDatastreamInfo().getControlGroup().equals("R") && !importRedirect)) {
211                        f4client.createOrUpdateRedirectNonRDFResource(dsPath,
212                                externalContentUrlMapper.mapURL(v.getExternalOrRedirectURL()));
213                        updateDatastreamProperties(version.getObject(), v, dsPath);
214                    } else {
215                        f4client.createOrUpdateNonRDFResource(dsPath, v.getContent(), v.getMimeType());
216                        updateDatastreamProperties(version.getObject(), v, dsPath);
217                    }
218                }
219
220                updateObjectProperties(version, objectPath, triplesToRemove, triplesToInsert);
221
222                f4client.createVersionSnapshot(objectPath,
223                        "imported-version-" + String.valueOf(version.getVersionIndex()));
224            }
225        } catch (final IOException e) {
226            throw new RuntimeException(e);
227        }
228    }
229
230    private List<DatastreamVersion> withRELSINTLast(final List<DatastreamVersion> orig) {
231        final List<DatastreamVersion> versionsWithRELSINTLast = new ArrayList<DatastreamVersion>(orig);
232        for (int i = 0; i < versionsWithRELSINTLast.size(); i ++) {
233            if (versionsWithRELSINTLast.get(i).getDatastreamInfo().getDatastreamId().equals("RELS-INT")) {
234                versionsWithRELSINTLast.add(versionsWithRELSINTLast.remove(i));
235                break;
236            }
237        }
238        return versionsWithRELSINTLast;
239    }
240
241    /**
242     * Evaluates if an object/datastream property is a date.
243     *
244     * @param uri   The predicate in question.
245     * @return      True if the property is a date.  False otherwise.
246     */
247    protected boolean isDateProperty(final String uri) {
248        return uri.equals("info:fedora/fedora-system:def/model#createdDate") ||
249                uri.equals("info:fedora/fedora-system:def/view#lastModifiedDate") ||
250                uri.equals("http://www.loc.gov/premis/rdf/v1#hasDateCreatedByApplication") ||
251                uri.equals("http://www.loc.gov/premis/rdf/v1#hasEventDateTime");
252    }
253
254    /**
255     * Updates object properties after mapping them from 3 to 4.
256     *
257     * @param version           Object version to reference
258     * @param objectPath        Destination path (in f4) for the object being migrated
259     * @param triplesToRemove   List of triples to remove from resource.
260     * @param triplesToInsert   List of triples to add to resource.
261
262     */
263    protected void updateObjectProperties(final ObjectVersionReference version,
264            final String objectPath,
265            final QuadAcc triplesToRemove,
266            final QuadDataAcc triplesToInsert) {
267        if (version.isFirstVersion()) {
268            // Migration event (current time)
269            final String now = getCurrentTimeInXSDDateTime();
270            if (now != null) {
271                addDateEvent(triplesToInsert,
272                        "http://id.loc.gov/vocabulary/preservation/eventType/mig",
273                        getCurrentTimeInXSDDateTime());
274            }
275
276            mapProperty("info:fedora/fedora-system:def/model#PID", version.getObject().getObjectInfo().getPid(),
277                    triplesToRemove, triplesToInsert, true);
278        }
279
280        if (version.isLastVersion()) {
281            for (final ObjectProperty p : version.getObjectProperties().listProperties()) {
282                mapProperty(p.getName(), p.getValue(), triplesToRemove, triplesToInsert, true);
283            }
284        }
285
286        // Update if there's triples to remove / add.
287        // Some may come from other datastreams like RELS-EXT and DC, not just
288        // in this function.
289        if (!triplesToInsert.getQuads().isEmpty() && !triplesToRemove.getQuads().isEmpty()) {
290            updateResourceProperties(objectPath, triplesToRemove, triplesToInsert, false);
291        }
292    }
293
294    /**
295     * WIP function to map properties from 3 to 4.
296     * Feel free to override this to suit your needs.
297     *
298     * @param origPred          Predicate of property to map from 3 to 4.
299     * @param obj               Object of property to map from 3 to 4.
300     * @param triplesToRemove   List of triples to remove from resource.
301     * @param triplesToInsert   List of triples to add to resource.
302     * @param isLiteral         TRUE if obj is a literal triple, FALSE if a URI
303     */
304    protected void mapProperty(final String origPred,
305                               final String obj,
306                               final QuadAcc triplesToRemove,
307                               final QuadDataAcc triplesToInsert,
308                               final Boolean isLiteral) {
309        String pred = origPred;
310        // Map dates and object state
311        if (customPropertyMapping != null && customPropertyMapping.containsKey(pred)) {
312            pred = customPropertyMapping.getProperty(pred);
313        } else if (pred.equals("info:fedora/fedora-system:def/model#createdDate")) {
314            // Handle created date seperately and exit early.
315            updateDateTriple(triplesToRemove, triplesToInsert,
316                    "http://fedora.info/definitions/v4/repository#created",
317                    obj);
318            return;
319        } else if (pred.equals("info:fedora/fedora-system:def/view#lastModifiedDate")) {
320            // Handle modified date seperately and exit early.
321            updateDateTriple(triplesToRemove, triplesToInsert,
322                    "http://fedora.info/definitions/v4/repository#lastModified",
323                    obj);
324            return;
325        }
326
327        if (isDateProperty(origPred)) {
328            updateDateTriple(triplesToRemove,
329                             triplesToInsert,
330                             pred,
331                             obj);
332        } else {
333            if (isLiteral) {
334                updateLiteralTriple(triplesToRemove,
335                                    triplesToInsert,
336                                    pred,
337                                    obj);
338            } else {
339                updateUriTriple(triplesToRemove,
340                               triplesToInsert,
341                               pred,
342                               obj);
343            }
344        }
345    }
346
347    /**
348     * WIP utility function to update datastream properties.
349     * Feel free to override this to suit your needs.
350     *
351     * @param obj    Object to operate upon
352     * @param v      Version of the datasream to update.
353     * @param dsPath resolved path (in f4) for the datastream
354     */
355    protected void updateDatastreamProperties(final ObjectReference obj,
356            final DatastreamVersion v, final String dsPath) {
357        final QuadDataAcc triplesToInsert = new QuadDataAcc();
358        final QuadAcc triplesToRemove = new QuadAcc();
359
360        final String createdDate = v.getCreated();
361
362        // Get some initial properties
363        if (v.isFirstVersionIn(obj)) {
364            // Migration event (current time)
365            final String now = getCurrentTimeInXSDDateTime();
366            if (now != null) {
367                addDateEvent(triplesToInsert,
368                        "http://id.loc.gov/vocabulary/preservation/eventType/mig",
369                        getCurrentTimeInXSDDateTime());
370            }
371
372            // Created date
373            if (createdDate != null) {
374                LOGGER.debug("Setting created date to " + createdDate + "...");
375                updateDateTriple(triplesToRemove,
376                        triplesToInsert,
377                        "http://fedora.info/definitions/v4/repository#created",
378                        createdDate);
379            }
380        }
381
382        // Get the rest of the properties from the last version.
383        if (v.isLastVersionIn(obj)) {
384            // DSID
385            final String dsid = v.getDatastreamInfo().getDatastreamId();
386            if (dsid != null) {
387                updateLiteralTriple(triplesToRemove,
388                                    triplesToInsert,
389                                    "http://purl.org/dc/terms/identifier",
390                                    dsid);
391            }
392
393            // The created date of the last version is the last modified date.
394            if (createdDate != null) {
395                updateDateTriple(triplesToRemove,
396                        triplesToInsert,
397                        "http://fedora.info/definitions/v4/repository#lastModified",
398                        createdDate);
399            }
400
401            // Label
402            final String label = v.getLabel();
403            if (label != null) {
404                updateLiteralTriple(triplesToRemove,
405                                    triplesToInsert,
406                                    "http://purl.org/dc/terms/title",
407                                    label);
408            }
409
410            // Object State
411            final String state = v.getDatastreamInfo().getState();
412            if (state != null) {
413                updateLiteralTriple(triplesToRemove,
414                                    triplesToInsert,
415                                    "http://fedora.info/definitions/1/0/access/objState",
416                                    state);
417            }
418
419            // Format URI
420            final String formatUri = v.getFormatUri();
421            if (formatUri != null) {
422                updateLiteralTriple(triplesToRemove,
423                                    triplesToInsert,
424                                    "http://www.loc.gov/premis/rdf/v1#formatDesignation",
425                                    formatUri);
426            }
427        }
428
429        // Only do the update if you've got stuff to change.
430        if (!triplesToInsert.getQuads().isEmpty() && !triplesToRemove.getQuads().isEmpty()) {
431            updateResourceProperties(dsPath, triplesToRemove, triplesToInsert, true);
432        }
433    }
434
435    /**
436     * Migrates a RELS-EXT datastream by splitting it apart into triples to
437     * update on the object it describes.
438     *
439     * @param v                 Version of the datasream to migrate.
440     * @param triplesToRemove   List of triples to remove from resource.
441     * @param triplesToInsert   List of triples to add to resource.
442     *
443     * @throws IOException on error
444     */
445    protected void migrateRelsExt(final DatastreamVersion v, final QuadAcc triplesToRemove,
446                                  final QuadDataAcc triplesToInsert) throws IOException {
447        // Get the identifier for the object this describes
448        final String objectUri = "info:fedora/" + v.getDatastreamInfo().getObjectInfo().getPid();
449
450        // Read the RDF
451        final Model m = ModelFactory.createDefaultModel();
452        m.read(v.getContent(), null);
453        final StmtIterator statementIt = m.listStatements();
454        while (statementIt.hasNext()) {
455            final Statement s = statementIt.nextStatement();
456            if (s.getSubject().getURI().equals(objectUri)) {
457                final String predicateUri = s.getPredicate().getURI();
458                if (s.getObject().isLiteral()) {
459                    mapProperty(predicateUri,
460                                s.getObject().asLiteral().getString(),
461                                triplesToRemove,
462                                triplesToInsert,
463                                true);
464                } else if (s.getObject().isURIResource()) {
465                    mapProperty(predicateUri,
466                                s.getObject().asResource().getURI(),
467                                triplesToRemove,
468                                triplesToInsert,
469                                false);
470                } else {
471                    throw new RuntimeException("No current handling for non-URI," +
472                            " non-Literal subjects in Fedora RELS-INT.");
473                }
474            } else {
475                throw new RuntimeException("Non-resource subject found: " + s.getSubject().getURI());
476            }
477        }
478    }
479
480    /**
481     * Migrates a RELS-INT datastream by splitting it apart and updating the
482     * other datastreams it describes.
483     *
484     * @param v     Version of the datasream to migrate.
485     *
486     * @throws java.io.IOException on error
487     * @throws java.lang.RuntimeException on error
488     */
489    protected void migrateRelsInt(final DatastreamVersion v) throws IOException, RuntimeException {
490        // Read the RDF.
491        final Model m = ModelFactory.createDefaultModel();
492        m.read(v.getContent(), null);
493        final StmtIterator statementIt = m.listStatements();
494        while (statementIt.hasNext()) {
495            // Get the datastream this triple describes.
496            final Statement s = statementIt.nextStatement();
497            final String dsUri = s.getSubject().getURI();
498            final String[] splitUri = dsUri.split("/");
499            final String dsId = splitUri[splitUri.length - 1];
500            final String dsPath = idMapper.mapDatastreamPath(v.getDatastreamInfo().getObjectInfo().getPid(), dsId);
501            if (!f4client.exists(dsPath)) {
502                f4client.createNonRDFPlaceholder(dsPath);
503                LOGGER.warn("The datastream \"" + dsId
504                        + "\" referenced in the RDF datastream \"" + v.getDatastreamInfo().getDatastreamId() + "\" on "
505                        + v.getDatastreamInfo().getObjectInfo().getPid() + " did not exist at "
506                        + v.getCreated() + ", making a placeholder!");
507            }
508
509            // Update this datastream with the RELS-INT RDF.
510            final QuadDataAcc triplesToInsert = new QuadDataAcc();
511            final QuadAcc triplesToRemove = new QuadAcc();
512
513            final String pred = s.getPredicate().getURI();
514
515            if (s.getObject().isLiteral()) {
516                mapProperty(pred,
517                            s.getObject().asLiteral().getString(),
518                            triplesToRemove,
519                            triplesToInsert,
520                            true);
521            } else if (s.getObject().isURIResource()) {
522                mapProperty(pred,
523                            s.getObject().asResource().getURI(),
524                            triplesToRemove,
525                            triplesToInsert,
526                            false);
527            } else {
528                throw new RuntimeException("No current handling for non-URI, non-Literal subjects in Fedora RELS-INT.");
529            }
530
531            updateResourceProperties(dsPath, triplesToRemove, triplesToInsert, true);
532        }
533    }
534
535    /**
536     * Migrates a DC datastream by shredding it into RDF properties and
537     * applying them directly to the object.
538     *
539     * @param v                 Version of the datasream to migrate.
540     * @param triplesToRemove   List of triples to remove from resource.
541     * @param triplesToInsert   List of triples to add to resource.
542     *
543     * @throws java.io.IOException on error
544     * @throws java.lang.RuntimeException on error
545     */
546    protected void migrateDc(final DatastreamVersion v, final QuadAcc triplesToRemove,
547                             final QuadDataAcc triplesToInsert) throws IOException, RuntimeException {
548        try {
549            final DC dc = DC.parseDC(v.getContent());
550            for (String uri : dc.getRepresentedElementURIs()) {
551                triplesToRemove.addTriple(new Triple(NodeFactory.createURI(""), NodeFactory.createURI(uri),
552                        NodeFactory.createVariable("o")));
553                for (String value : dc.getValuesForURI(uri)) {
554                    triplesToInsert.addTriple(new Triple(NodeFactory.createURI(""), NodeFactory.createURI(uri),
555                            NodeFactory.createLiteral(value)));
556                    LOGGER.debug("Adding " + uri + " value " + value);
557                }
558            }
559        } catch (JAXBException e) {
560            throw new RuntimeException("Error parsing DC datastream " + v.getVersionId());
561        }
562    }
563
564    /**
565     * Utility function for updating a FedoraResource's properties.
566     *
567     * @param path              Path to the fedora resource to update.
568     * @param triplesToRemove   List of triples to remove from resource.
569     * @param triplesToInsert   List of triples to add to resource.
570     * @param isNonRDF          true if the resource is a non-RDF resource.
571     *
572     * @throws RuntimeException Possible FedoraExcpetions and IOExceptions
573     */
574    protected void updateResourceProperties(final String path,
575            final QuadAcc triplesToRemove,
576            final QuadDataAcc triplesToInsert,
577            final boolean isNonRDF) throws RuntimeException {
578        try {
579            final UpdateRequest updateRequest = UpdateFactory.create();
580            namespacePrefixMapper.setPrefixes(updateRequest);
581            updateRequest.add(new UpdateDeleteWhere(triplesToRemove));
582            updateRequest.add(new UpdateDataInsert(triplesToInsert));
583            final ByteArrayOutputStream sparqlUpdate = new ByteArrayOutputStream();
584            updateRequest.output(new IndentedWriter(sparqlUpdate));
585            LOGGER.trace("SPARQL: " + sparqlUpdate.toString("UTF-8"));
586            if (isNonRDF) {
587                f4client.updateNonRDFResourceProperties(path, sparqlUpdate.toString("UTF-8"));
588            } else {
589                f4client.updateResourceProperties(path, sparqlUpdate.toString("UTF-8"));
590            }
591            suffix = 0;
592        } catch (final IOException e) {
593            throw new RuntimeException(e);
594        }
595    }
596
597    /**
598     * Utility function for updating a literal triple.
599     *
600     * @param triplesToRemove   List of triples to remove from resource.
601     * @param triplesToInsert   List of triples to add to resource.
602     * @param predicate         Predicate of relationship (assumed to be URI).
603     * @param object            Object of relationship (assumed to be literal).
604     */
605    protected void updateLiteralTriple(final QuadAcc triplesToRemove,
606                                       final QuadDataAcc triplesToInsert,
607                                       final String predicate,
608                                       final String object) {
609        triplesToRemove.addTriple(new Triple(NodeFactory.createURI(""),
610                NodeFactory.createURI(predicate),
611                NodeFactory.createVariable("o" + String.valueOf(suffix))));
612        triplesToInsert.addTriple(new Triple(NodeFactory.createURI(""),
613                NodeFactory.createURI(predicate),
614                NodeFactory.createLiteral(object)));
615        suffix++;
616    }
617
618    /**
619     * Utility function for updating a uri triple.
620     *
621     * @param triplesToRemove   List of triples to remove from resource.
622     * @param triplesToInsert   List of triples to add to resource.
623     * @param predicate         Predicate of relationship (assumed to be URI).
624     * @param object            Object of relationship (assumed to URI).
625     */
626    protected void updateUriTriple(final QuadAcc triplesToRemove,
627                                   final QuadDataAcc triplesToInsert,
628                                   final String predicate,
629                                   final String object) {
630        final String newObjectUri = resolveInternalURI(object);
631        triplesToRemove.addTriple(new Triple(NodeFactory.createURI(""),
632                                             NodeFactory.createURI(predicate),
633                                             NodeFactory.createVariable("o" + String.valueOf(suffix))));
634        triplesToInsert.addTriple(new Triple(NodeFactory.createURI(""),
635                                             NodeFactory.createURI(predicate),
636                                             NodeFactory.createURI(newObjectUri)));
637        suffix++;
638    }
639
640    /**
641     * Takes a URI (String) and if it appears to be an internal Fedora URI ("info:fedora/pid")
642     * the migrated URI for that resource is returned (and a placeholder is created in the
643     * repository if it doesn't already exist).  Otherwise the value is returned unmodified.
644     *
645     * @param uri to be resolved
646     *
647     * @return string which is either the migrated URI or the unmodified URI
648     */
649    protected String resolveInternalURI(final String uri) {
650        if (uri.startsWith("info:fedora/")) {
651            final String path = idMapper.mapObjectPath(uri.substring("info:fedora/".length()));
652            f4client.createPlaceholder(path);
653            return f4client.getRepositoryUrl() + path;
654        }
655        return uri;
656    }
657
658    /**
659     * Utility function for updating a date triple.
660     *
661     * @param triplesToRemove   List of triples to remove from resource.
662     * @param triplesToInsert   List of triples to add to resource.
663     * @param predicate         Predicate of relationship (assumed to be URI).
664     * @param object            Object of relationship (assumed to be literal).
665     */
666    protected void updateDateTriple(final QuadAcc triplesToRemove,
667            final QuadDataAcc triplesToInsert,
668            final String predicate,
669            final String object) {
670        triplesToRemove.addTriple(new Triple(NodeFactory.createURI(""),
671                NodeFactory.createURI(predicate),
672                NodeFactory.createVariable("o" + String.valueOf(suffix))));
673        triplesToInsert.addTriple(new Triple(NodeFactory.createURI(""),
674                NodeFactory.createURI(predicate),
675                NodeFactory.createLiteral(object, XSDDatatype.XSDdateTime)));
676        suffix++;
677    }
678
679    /**
680     * Utility function for adding a premis date event.  Current
681     * implementation utilizes a blank node.
682     *
683     * @param triplesToInsert   List of triples to add to resource.
684     * @param eventTypeURI      Type of premis event.
685     * @param object            Object of relationship (e.g. the date.  Assumed to be literal).
686     */
687    protected void addDateEvent(final QuadDataAcc triplesToInsert,
688            final String eventTypeURI,
689            final String object) {
690        final String eventPred = "http://www.loc.gov/premis/rdf/v1#hasEvent";
691        final String eventTypePred = "http://www.loc.gov/premis/rdf/v1#hasEventType";
692        final String eventDatePred = "http://www.loc.gov/premis/rdf/v1#hasEventDateTime";
693        final Node bnode = NodeFactory.createAnon();
694
695        triplesToInsert.addTriple(new Triple(NodeFactory.createURI(""),
696                NodeFactory.createURI(eventPred),
697                bnode));
698        triplesToInsert.addTriple(new Triple(bnode,
699                NodeFactory.createURI(eventTypePred),
700                NodeFactory.createURI(eventTypeURI)));
701        triplesToInsert.addTriple(new Triple(bnode,
702                NodeFactory.createURI(eventDatePred),
703                NodeFactory.createLiteral(object, XSDDatatype.XSDdateTime)));
704    }
705
706    /**
707     * Utility function to get the current time properly formatted for SPARQL
708     * or XML.
709     *
710     * @return  String representing current time in XSDdateTime format (null if error).
711     */
712    protected String getCurrentTimeInXSDDateTime() {
713        try {
714            final GregorianCalendar cal = new GregorianCalendar();
715            cal.setTime(new Date());
716            final XMLGregorianCalendar now = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
717            return now.toString();
718        } catch (final DatatypeConfigurationException e) {
719            LOGGER.error("Error converting date object to proper format!", e);
720            return null;
721        }
722    }
723}