/**
* JHOVE2 - Next-generation architecture for format-aware characterization
*
* Copyright (c) 2009 by The Regents of the University of California,
* Ithaka Harbors, Inc., and The Board of Trustees of the Leland Stanford
* Junior University.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of the University of California/California Digital
* Library, Ithaka Harbors/Portico, or Stanford University, nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jhove2.module.identify;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import org.jhove2.annotation.ReportableProperty;
import org.jhove2.app.util.FeatureConfigurationUtil;
import org.jhove2.core.I8R;
import org.jhove2.core.JHOVE2;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.Message;
import org.jhove2.core.Message.Context;
import org.jhove2.core.Message.Severity;
import org.jhove2.core.format.FormatIdentification;
import org.jhove2.core.format.FormatIdentification.Confidence;
import org.jhove2.core.io.Input;
import org.jhove2.core.source.Source;
import org.jhove2.persist.ModuleAccessor;
import com.sleepycat.persist.model.NotPersistent;
import com.sleepycat.persist.model.Persistent;
import uk.gov.nationalarchives.droid.JHOVE2IAnalysisController;
import uk.gov.nationalarchives.droid.IdentificationFile;
import uk.gov.nationalarchives.droid.FileFormatHit;
import uk.gov.nationalarchives.droid.signatureFile.FileFormat;
import uk.gov.nationalarchives.droid.ConfigFile;
import uk.gov.nationalarchives.droid.signatureFile.FFSignatureFile;
/**
* Identifier that wraps the DROID identifier tool
* Note that this wrapper is using a "light" version of DROID --
* a modified version of the DROID code which has been stripped of
* DROID GUI, command line, reporting, and profiling capabilities,
* and merely uses the API to run the DROID identifier engine
* against a file
*
* @author smorrissey
*/
@Persistent
public class DROIDIdentifier
extends AbstractFileSourceIdentifier
implements SourceIdentifier
{
/** Framework version identifier. */
public static final String VERSION = "2.0.0";
/** Framework release date. */
public static final String RELEASE = "2010-09-10";
/** Framework rights statement. */
public static final String RIGHTS = "Copyright 2010 by The Regents of the University of California, "
+ "Ithaka Harbors, Inc., and The Board of Trustees of the Leland "
+ "Stanford Junior University. "
+ "Available under the terms of the BSD license.";
/** DROID product name */
public static final String DROID_NAME = "DROID (Digital Record Object Identification)";
/** Bean name for bean for properties file mapping PUIDs to JHOVE2 format identifiers */
public static final String PUIDMAP_BEANNAME = "DroidMap";
/** Bean name for bean for properties file mapping JHOVE2 format identifiers to JHOVE2 format bean names */
public static final String JHOVE2BEANMAP_BEANNAME = "FormatBeanMap";
/** DROID Configuration file name */
private String configurationFileName = null;
/** DROID signature file name */
private String signatureFileName = null;
/** map from DROID PUIDs to JHOVE2 format ids */
@NotPersistent
private static ConcurrentMap<String, String> puidToJhoveId;
/** static member to cache parsed droid config file */
@NotPersistent
private static ConfigFile cachedConfigFile = null;
/** static member to cache parsed droid signature file */
@NotPersistent
private static FFSignatureFile cachedSigFile = null;
/**Instantiate a new <code>DROIDIdentifier</code> module that wraps DROID.
* @throws JHOVE2Exception
*/
public DROIDIdentifier()
throws JHOVE2Exception
{
this(null);
}
/**Instantiate a new <code>DROIDIdentifier</code> module that wraps DROID.
* @param moduleAccessor persistence manager
* @throws JHOVE2Exception
*/
public DROIDIdentifier(ModuleAccessor moduleAccessor)
throws JHOVE2Exception
{
this(null, null, moduleAccessor);
}
/**Instantiate a new <code>DROIDIdentifier</code> module that wraps DROID.
* @param configFileName path to DROID configuration file
* @param sigFileName path to DROID signature file
* @param moduleAccessor persistence manager
* @throws JHOVE2Exception
*/
public DROIDIdentifier(String configFileName, String sigFileName,
ModuleAccessor moduleAccessor)
throws JHOVE2Exception
{
super(VERSION, RELEASE, RIGHTS, Scope.Generic, moduleAccessor);
this.setConfigFileName(configFileName);
this.setSigFileName(sigFileName);
}
/**
* Presumptively identify the format of a source unit.
*
* @param jhove2
* JHOVE2 framework
* @param source
* Source unit
* @return Set of presumptive format identifications
* @throws IOException
* I/O exception encountered identifying the source unit
* @throws JHOVE2Exception
*/
@Override
public Set<FormatIdentification> identify(JHOVE2 jhove2, Source source, Input input)
throws IOException, JHOVE2Exception
{
DROIDWrapper droid = new DROIDWrapper();
Set<FormatIdentification> presumptiveFormatIds =
new TreeSet<FormatIdentification>();
try {
ConfigFile configFile = getCachedConfigFile(this.getConfigurationFile());
FFSignatureFile sigFile = getCachedSignatureFile(configFile, this.getSignatureFile());
droid.setConfigFile(configFile);
droid.setSigFile(sigFile);
IdentificationFile idf = droid.identify(source, input);
boolean matchFound = this.matchFound(idf, jhove2, source);
if (matchFound){
String msgText = idf.getWarning();
Message idWarningMessage = null;
if (msgText != null && msgText.length()>0) {
Object [] messageParms = new Object []{msgText};
idWarningMessage = new Message(Severity.WARNING,
Context.OBJECT,
"org.jhove2.module.identify.DROIDIdentifier.identify.idWarningMessage",
messageParms, jhove2.getConfigInfo());
}
for (int i=0; i<idf.getNumHits(); i++){
ArrayList<Message> idMessages = new ArrayList<Message>();
ArrayList<Message> unmatchedPUIDMessages =
new ArrayList<Message>();
Message hitWarningMsg = null;
if (idWarningMessage != null){
idMessages.add(idWarningMessage);
}
FileFormatHit ffh = idf.getHit(i);
String hitWarning = ffh.getHitWarning();
if (hitWarning != null && hitWarning.length()>0) {
Object[]messageParms = new Object[]{hitWarning};
hitWarningMsg = new Message(Severity.WARNING,
Context.OBJECT,
"org.jhove2.module.identify.DROIDIdentifier.identify.hitWarningMsg",
messageParms, jhove2.getConfigInfo());
idMessages.add(hitWarningMsg);
}
FileFormat ff = ffh.getFileFormat();
String puid = ff.getPUID();
I8R droidId = new I8R(puid, I8R.Namespace.PUID);
Confidence jhoveConfidence = this.getJHOVE2Confidence(ffh);
// look up the JHOVE2 format id corresponding to DROID format id (PUID)
String jhoveFormatId = null;
if (! getPUIDtoJ2ID(jhove2).containsKey(puid)){
// if there is no match, attach an ERROR message to the FormatIdentification,
// and use the default JHOVE2 format
Object[]messageParms = new Object[]{puid};
Message missingPuid = new Message(Severity.ERROR,
Context.PROCESS,
"org.jhove2.module.identify.DROIDIdentifier.identify.missingPUID",
messageParms, jhove2.getConfigInfo());
unmatchedPUIDMessages.add(missingPuid);
}
else {
jhoveFormatId = getPUIDtoJ2ID(jhove2).get(puid);
}
idMessages.addAll(unmatchedPUIDMessages);
I8R jhoveId = null;
if (jhoveFormatId != null) {
jhoveId = new I8R(jhoveFormatId);
}
FormatIdentification fi =
new FormatIdentification(jhoveId, jhoveConfidence,
this.getReportableIdentifier(),
droidId, idMessages);
presumptiveFormatIds.add(fi);
}
}
}
catch (IOException e) {
throw e;
}
catch (Exception ex) {
throw new JHOVE2Exception("Failure running DROID " + ex.getMessage(),ex);
}
return presumptiveFormatIds;
}
/**
* If DROID config file has not yet been parsed, parse and return it;
* Otherwise simply returns parsed config file
* @param configFilePath path to DROID config file
* @return parsed config file object
* @throws Exception
*/
private static synchronized ConfigFile getCachedConfigFile(String configFilePath)
throws Exception
{
if (cachedConfigFile == null) {
cachedConfigFile = DROIDWrapper.parseConfigFile(configFilePath);
}
return cachedConfigFile;
}
/**
* If DROID signature file has not yet been parsed, parses and returns it;
* Otherwise, just returns parsed signature file
* @param configFile parsed DROID config file object
* @param sigFilePath path to DROID signature file
* @return parsed signature file contents
* @throws Exception
*/
private static synchronized FFSignatureFile getCachedSignatureFile(ConfigFile configFile,
String sigFilePath)
throws Exception
{
if (cachedSigFile == null) {
cachedSigFile = DROIDWrapper.parseSignatureFile(configFile,
sigFilePath);
}
return cachedSigFile;
}
/**
* Return the name of the configuration file.
* @return Configuration file name
*/
public String getConfigurationFileName() {
return configurationFileName;
}
/** Get DROID configuration file path.
* @return DROID configuration file path
* @throws JHOVE2Exception
*/
@ReportableProperty(order = 1, value = "DROID configuration file path.")
public String getConfigurationFile()
throws JHOVE2Exception
{
String path = FeatureConfigurationUtil.getFilePathFromClasspath(this.getConfigurationFileName(), "DROID config file");
return path;
}
/**
* Map from DROID confidence levels to JHOVE2 confidence levels
* @param ffh File format hit containing DROID confidence level
* @return JHOVE2 confidence level for this identifier
*/
protected Confidence getJHOVE2Confidence(FileFormatHit ffh) {
int droidConfidence = ffh.getHitType();
Confidence jhoveConfidence;
switch (droidConfidence){
case JHOVE2IAnalysisController.HIT_TYPE_POSITIVE_SPECIFIC:
jhoveConfidence = Confidence.PositiveSpecific;
break;
case JHOVE2IAnalysisController.HIT_TYPE_POSITIVE_GENERIC_OR_SPECIFIC:
jhoveConfidence = Confidence.PositiveGeneric;
break;
case JHOVE2IAnalysisController.HIT_TYPE_POSITIVE_GENERIC:
jhoveConfidence = Confidence.PositiveGeneric;
break;
case JHOVE2IAnalysisController.HIT_TYPE_TENTATIVE:
jhoveConfidence = Confidence.Tentative;
break;
default:
jhoveConfidence = Confidence.Negative;
}
return jhoveConfidence;
}
/**
* Gets the mapping from DROID PUID to JHOVE2 Format Identifier.
* Initializes the static map on first invocation.
*
* @return DROID PUID to JHOVE2 Format Identifier map
* @throws JHOVE2Exception
*/
public static ConcurrentMap<String,String> getPUIDtoJ2ID(JHOVE2 jhove2)
throws JHOVE2Exception
{
if (puidToJhoveId == null){
puidToJhoveId = jhove2.getConfigInfo().getFormatAliasIdsToJ2Ids(I8R.Namespace.PUID);
}
return puidToJhoveId;
}
/** Get DROID signature file path.
* @return DROID signature file path
* @throws JHOVE2Exception
*/
@ReportableProperty(order = 2, value = "DROID signature file path.")
public String getSignatureFile() throws JHOVE2Exception {
String path = FeatureConfigurationUtil.getFilePathFromClasspath(this.getSignatureFileName(), "DROID signature file");
return path;
}
/**
* Return the name of the signature file
* @return the signatureFileName
*/
public String getSignatureFileName() {
return signatureFileName;
}
/**
* Checks DROID file classification codes to see if DROID was able to match file
* NOTE: SIDE EFFECTS: if there are errors, or if no identifier can be made, or
* if DROID returns warning message, this method populates
* the relevant Message member of this object instance
* @param idf DROID {@link uk.gov.nationalarchives.droid.IdentificationFile} object
* @param jhove2
* @return true if DROID able to identify file; otherwise false
* @throws JHOVE2Exception
*/
protected boolean matchFound(IdentificationFile idf, JHOVE2 jhove2, Source source)
throws JHOVE2Exception
{
boolean matchFound = false;
int classification = idf.getClassification();
String msgText = null;
switch (classification){
case JHOVE2IAnalysisController.FILE_CLASSIFICATION_NOHIT:
msgText = idf.getWarning();
if (msgText==null){
msgText = new String("");
}
Object[]messageParms = new Object[]{msgText};
Message fileNotIdentifiedMessage = new Message(Severity.WARNING,
Context.OBJECT,
"org.jhove2.module.identify.DROIDIdentifier.fileNotIdentifiedMessage",
messageParms, jhove2.getConfigInfo());
source.addMessage(fileNotIdentifiedMessage);
break;
case JHOVE2IAnalysisController.FILE_CLASSIFICATION_NOTCLASSIFIED:
msgText = idf.getWarning();
if (msgText==null){
msgText = new String("");
}
messageParms = new Object[]{msgText};
Message fileNotRunMessage = new Message(Severity.ERROR,
Context.PROCESS,
"org.jhove2.module.identify.DROIDIdentifier.fileNotRunMessage",
messageParms, jhove2.getConfigInfo());
source.addMessage(fileNotRunMessage);
break;
case JHOVE2IAnalysisController.FILE_CLASSIFICATION_ERROR:
msgText = idf.getWarning();
if (msgText==null){
msgText = new String("");
}
messageParms = new Object[]{msgText};
Message fileErrorMessage = new Message(Severity.ERROR,
Context.PROCESS,
"org.jhove2.module.identify.DROIDIdentifier.fileErrorMessage",
messageParms, jhove2.getConfigInfo());
source.addMessage(fileErrorMessage);
break;
default:
matchFound = true;
break;
}// end switch
return matchFound;
}
/**
* Set the name of the config file
* @param configurationFileName the configurationFileName to set
*/
public void setConfigFileName(String configFileName) {
this.configurationFileName = configFileName;
}
/**
* Set the name of the signature file
* @param signatureFileName the signatureFileName to set
*/
public void setSigFileName(String sigFileName) {
this.signatureFileName = sigFileName;
}
}