/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* https://github.com/CILEA/dspace-cris/wiki/License
*/
package org.dspace.app.cris.integration;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.app.cris.configuration.RelationPreferenceConfiguration;
import org.dspace.app.cris.model.CrisConstants;
import org.dspace.app.cris.model.RelationPreference;
import org.dspace.app.cris.model.ResearcherPage;
import org.dspace.app.cris.model.RestrictedField;
import org.dspace.app.cris.service.ApplicationService;
import org.dspace.app.cris.service.RelationPreferenceService;
import org.dspace.app.cris.util.ResearcherPageUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.browse.BrowseEngine;
import org.dspace.browse.BrowseException;
import org.dspace.browse.BrowseIndex;
import org.dspace.browse.BrowseInfo;
import org.dspace.browse.BrowseItem;
import org.dspace.browse.BrowserScope;
import org.dspace.content.DCValue;
import org.dspace.content.Item;
import org.dspace.content.ItemIterator;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
import org.dspace.content.authority.AuthorityDAO;
import org.dspace.content.authority.AuthorityDAOFactory;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.Choices;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.core.PluginManager;
import org.dspace.eperson.EPerson;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.utils.DSpace;
/**
* Utility class for performing Item to ReseacherPage binding
*
* @author cilea
*
*/
public class BindItemToRP
{
/** the logger */
private static Logger log = Logger.getLogger(BindItemToRP.class);
/**
* the name of the browse index where lookup for potential matches.
* Configured in the dspace.cfg with the property
* <code>researcherpage.browseindex</code>
*/
private static final String researcherPotentialMatchLookupBrowserIndex = ConfigurationManager
.getProperty(CrisConstants.CFG_MODULE, "researcherpage.browseindex");
private RelationPreferenceService relationPreferenceService;
public static int automaticClaim(Context context, ResearcherPage rp)
throws SQLException, AuthorizeException
{
context.turnOffAuthorisationSystem();
DSpace dspace = new DSpace();
ApplicationService applicationService = dspace.getServiceManager()
.getServiceByName("applicationService",
ApplicationService.class);
RelationPreferenceService relationPreferenceService = dspace
.getServiceManager()
.getServiceByName(
"org.dspace.app.cris.service.RelationPreferenceService",
RelationPreferenceService.class);
List<RelationPreference> rejected = new ArrayList<RelationPreference>();
for (RelationPreferenceConfiguration configuration : relationPreferenceService
.getConfigurationService().getList())
{
if (configuration.getRelationConfiguration().getRelationClass().equals(Item.class))
{
rejected = applicationService
.findRelationsPreferencesByUUIDByRelTypeAndStatus(
rp.getUuid(), configuration.getRelationConfiguration().getRelationName(),
RelationPreference.UNLINKED);
}
}
EPerson eperson = EPerson.find(context, rp.getEpersonID());
ItemIterator items = Item.findBySubmitter(context, eperson);
List<MetadataField> mfs = metadataFieldWithAuthorityRP(context);
int count = 0;
while (items.hasNext())
{
Item item = items.next();
if (rejected == null || !rejected.contains(item.getID()))
{
boolean found = false;
for (MetadataField mf : mfs)
{
String schema = MetadataSchema.find(context,
mf.getSchemaID()).getName();
String element = mf.getElement();
String qualifier = mf.getQualifier();
DCValue[] values = item.getMetadata(schema, element,
qualifier, Item.ANY);
item.clearMetadata(schema, element, qualifier, Item.ANY);
for (DCValue val : values)
{
if (val.authority == null
&& val.value != null
&& StringUtils.containsIgnoreCase(val.value,
eperson.getLastName().trim()))
{
val.authority = ResearcherPageUtils
.getPersistentIdentifier(rp);
val.confidence = Choices.CF_ACCEPTED;
found = true;
}
item.addMetadata(schema, element, qualifier,
val.language, val.value, val.authority,
val.confidence);
}
}
if (found)
{
item.update();
count++;
}
}
}
context.restoreAuthSystemState();
return count;
}
public static int countPotentialMatch(Context context, ResearcherPage rp)
throws SQLException, AuthorizeException, IOException
{
return getPotentialMatch(context, rp).size();
}
public static long countPendingMatch(Context context, ResearcherPage rp)
throws SQLException
{
AuthorityDAO dao = AuthorityDAOFactory.getInstance(context);
return dao.countIssuedItemsByAuthorityValueInAuthority(
RPAuthority.RP_AUTHORITY_NAME,
ResearcherPageUtils.getPersistentIdentifier(rp));
}
public static Set<Integer> getPotentialMatch(Context context,
ResearcherPage researcher) throws SQLException, AuthorizeException,
IOException
{
Set<Integer> invalidIds = new HashSet<Integer>();
ItemIterator iter = null;
try
{
iter = getPendingMatch(context, researcher);
while (iter.hasNext())
{
invalidIds.add(iter.nextID());
}
}
finally
{
if (iter != null)
iter.close();
}
DSpace dspace = new DSpace();
ApplicationService applicationService = dspace.getServiceManager()
.getServiceByName("applicationService",
ApplicationService.class);
RelationPreferenceService relationPreferenceService = dspace
.getServiceManager()
.getServiceByName(
"org.dspace.app.cris.service.RelationPreferenceService",
RelationPreferenceService.class);
List<RelationPreference> rejected = new ArrayList<RelationPreference>();
for (RelationPreferenceConfiguration configuration : relationPreferenceService
.getConfigurationService().getList())
{
if (configuration.getRelationConfiguration().getRelationClass().equals(Item.class))
{
rejected = applicationService
.findRelationsPreferencesByUUIDByRelTypeAndStatus(
researcher.getUuid(), configuration.getRelationConfiguration().getRelationName(),
RelationPreference.UNLINKED);
}
}
for (RelationPreference relationPreference : rejected)
{
invalidIds.add(relationPreference.getItemID());
}
List<NameResearcherPage> names = new LinkedList<NameResearcherPage>();
String authority = researcher.getCrisID();
int id = researcher.getId();
NameResearcherPage name = new NameResearcherPage(
researcher.getFullName(), authority, id, invalidIds);
names.add(name);
RestrictedField field = researcher.getPreferredName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_1 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_1);
}
field = researcher.getTranslatedName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_2 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_2);
}
for (RestrictedField r : researcher.getVariants())
{
if (r != null && r.getValue() != null && !r.getValue().isEmpty())
{
NameResearcherPage name_3 = new NameResearcherPage(
r.getValue(), authority, id, invalidIds);
names.add(name_3);
}
}
Set<Integer> result = new HashSet<Integer>();
try
{
BrowseIndex bi = BrowseIndex
.getBrowseIndex(researcherPotentialMatchLookupBrowserIndex);
// now start up a browse engine and get it to do the work for us
BrowseEngine be = new BrowseEngine(context);
int count = 1;
for (NameResearcherPage tempName : names)
{
log.debug("work on " + tempName.getName() + " with identifier "
+ tempName.getPersistentIdentifier() + " (" + count
+ " of " + names.size() + ")");
// set up a BrowseScope and start loading the values into it
BrowserScope scope = new BrowserScope(context);
scope.setBrowseIndex(bi);
// scope.setOrder(order);
scope.setFilterValue(tempName.getName());
// scope.setFilterValueLang(valueLang);
// scope.setJumpToItem(focus);
// scope.setJumpToValue(valueFocus);
// scope.setJumpToValueLang(valueFocusLang);
// scope.setStartsWith(startsWith);
// scope.setOffset(offset);
scope.setResultsPerPage(Integer.MAX_VALUE);
// scope.setSortBy(sortBy);
scope.setBrowseLevel(1);
// scope.setEtAl(etAl);
BrowseInfo binfo = be.browse(scope);
log.debug("Find " + binfo.getResultCount()
+ "item(s) in browsing...");
for (BrowseItem bitem : binfo.getBrowseItemResults())
{
if (!invalidIds.contains(bitem.getID()))
{
result.add(bitem.getID());
}
}
}
}
catch (BrowseException be)
{
log.error(LogManager.getHeader(context, "getPotentialMatch",
"researcher=" + researcher.getCrisID()), be);
}
return result;
}
private static List<MetadataField> metadataFieldWithAuthorityRP(
Context context) throws SQLException
{
// find all metadata with authority support
MetadataField[] fields = MetadataField.findAll(context);
List<MetadataField> fieldsWithAuthoritySupport = new LinkedList<MetadataField>();
for (MetadataField mf : fields)
{
String schema = (MetadataSchema.find(context, mf.getSchemaID()))
.getName();
String mdstring = schema
+ "."
+ mf.getElement()
+ (mf.getQualifier() == null ? "" : "." + mf.getQualifier());
String choicesPlugin = ConfigurationManager
.getProperty("choices.plugin." + mdstring);
if (choicesPlugin != null)
{
choicesPlugin = choicesPlugin.trim();
}
if ((RPAuthority.RP_AUTHORITY_NAME.equals(choicesPlugin)))
{
fieldsWithAuthoritySupport.add(mf);
}
}
return fieldsWithAuthoritySupport;
}
public static ItemIterator getPendingMatch(Context context,
ResearcherPage rp) throws SQLException, AuthorizeException,
IOException
{
AuthorityDAO dao = AuthorityDAOFactory.getInstance(context);
return dao.findIssuedByAuthorityValueInAuthority(
RPAuthority.RP_AUTHORITY_NAME,
ResearcherPageUtils.getPersistentIdentifier(rp));
}
/**
* Search potential matches for all the ResearcherPage supplied. The
* algorithm search for any researcher page and any researcher's name
* (regardless the visibility attribute) all the items published in DSpace
* using the Browse System (@link
* using the Browse System (@link #researcherPotentialMatchLookupBrowserIndex}, if a match is found
* and there is not an existent authority key for the metadata then the rp
* identifier of the matching researcher page is used as authority key and a
* confidence value is attributed as follow:
* <ul>
* <li>{@link Choices.CF_UNCERTAIN} if there is only a potential matching
* researcher page</li>
* <li>{@link Choices.CF_AMBIGUOUS} if there are more than one potential
* matching reseacher pages</li>
* </ul>
*
* @param rps
* the list of ResearcherPage
* @param applicationService
* the ApplicationService
*
* @see #researcherPotentialMatchLookupBrowserIndex
* @see Choices#CF_UNCERTAIN
* @see Choices#CF_AMBIGUOUS
*
*/
public static void work(List<ResearcherPage> rps,
RelationPreferenceService relationPreferenceService)
{
log.debug("Working...building names list");
List<NameResearcherPage> names = new LinkedList<NameResearcherPage>();
for (ResearcherPage researcher : rps)
{
Set<Integer> invalidIds = new HashSet<Integer>();
List<RelationPreference> rejected = new ArrayList<RelationPreference>();
for (RelationPreferenceConfiguration configuration : relationPreferenceService
.getConfigurationService().getList())
{
if (configuration.getRelationConfiguration().getRelationClass().equals(Item.class))
{
rejected = relationPreferenceService
.findRelationsPreferencesByUUIDByRelTypeAndStatus(
researcher.getUuid(), configuration.getRelationConfiguration().getRelationName(),
RelationPreference.UNLINKED);
}
}
for (RelationPreference relationPreference : rejected)
{
invalidIds.add(relationPreference.getItemID());
}
String authority = researcher.getCrisID();
int id = researcher.getId();
NameResearcherPage name = new NameResearcherPage(
researcher.getFullName(), authority, id, invalidIds);
names.add(name);
RestrictedField field = researcher.getPreferredName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_1 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_1);
}
field = researcher.getTranslatedName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_2 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_2);
}
for (RestrictedField r : researcher.getVariants())
{
if (r != null && r.getValue() != null
&& !r.getValue().isEmpty())
{
NameResearcherPage name_3 = new NameResearcherPage(
r.getValue(), authority, id, invalidIds);
names.add(name_3);
}
}
}
log.debug("...DONE building names list size " + names.size());
log.debug("Create DSpace context and use browse indexing");
Context context = null;
try
{
context = new Context();
context.setIgnoreAuthorization(true);
List<MetadataField> fieldsWithAuthoritySupport = metadataFieldWithAuthorityRP(context);
BrowseIndex bi = BrowseIndex
.getBrowseIndex(researcherPotentialMatchLookupBrowserIndex);
// now start up a browse engine and get it to do the work for us
BrowseEngine be = new BrowseEngine(context);
int count = 1;
for (NameResearcherPage tempName : names)
{
log.info("work on " + tempName.getName() + " with identifier "
+ tempName.getPersistentIdentifier() + " (" + count
+ " of " + names.size() + ")");
// set up a BrowseScope and start loading the values into it
BrowserScope scope = new BrowserScope(context);
scope.setBrowseIndex(bi);
// scope.setOrder(order);
scope.setFilterValue(tempName.getName());
// scope.setFilterValueLang(valueLang);
// scope.setJumpToItem(focus);
// scope.setJumpToValue(valueFocus);
// scope.setJumpToValueLang(valueFocusLang);
// scope.setStartsWith(startsWith);
// scope.setOffset(offset);
scope.setResultsPerPage(Integer.MAX_VALUE);
// scope.setSortBy(sortBy);
scope.setBrowseLevel(1);
// scope.setEtAl(etAl);
BrowseInfo binfo = be.browse(scope);
log.info("Find " + binfo.getResultCount()
+ "item(s) in browsing...");
bindItemsToRP(relationPreferenceService, context,
fieldsWithAuthoritySupport, tempName,
binfo.getItemResults(context));
count++;
}
}
catch (Exception e)
{
log.error(e.getMessage(), e);
throw new RuntimeException(e.getMessage(), e);
}
finally
{
if (context != null && context.isValid())
{
context.abort();
}
}
}
public static void bindItemsToRP(RelationPreferenceService relationPreferenceService,
Context context, ResearcherPage researcher, Item[] items)
throws SQLException, BrowseException, AuthorizeException
{
String authority = researcher.getCrisID();
int id = researcher.getId();
List<NameResearcherPage> names = new LinkedList<NameResearcherPage>();
Set<Integer> invalidIds = new HashSet<Integer>();
List<RelationPreference> rejected = new ArrayList<RelationPreference>();
for (RelationPreferenceConfiguration configuration : relationPreferenceService
.getConfigurationService().getList())
{
if (configuration.getRelationConfiguration().getRelationClass().equals(Item.class))
{
rejected = relationPreferenceService
.findRelationsPreferencesByUUIDByRelTypeAndStatus(
researcher.getUuid(), configuration.getRelationConfiguration().getRelationName(),
RelationPreference.UNLINKED);
}
}
for (RelationPreference relationPreference : rejected)
{
invalidIds.add(relationPreference.getItemID());
}
NameResearcherPage name = new NameResearcherPage(
researcher.getFullName(), authority, id, invalidIds);
names.add(name);
RestrictedField field = researcher.getPreferredName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_1 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_1);
}
field = researcher.getTranslatedName();
if (field != null && field.getValue() != null
&& !field.getValue().isEmpty())
{
NameResearcherPage name_2 = new NameResearcherPage(
field.getValue(), authority, id, invalidIds);
names.add(name_2);
}
for (RestrictedField r : researcher.getVariants())
{
if (r != null && r.getValue() != null && !r.getValue().isEmpty())
{
NameResearcherPage name_3 = new NameResearcherPage(
r.getValue(), authority, id, invalidIds);
names.add(name_3);
}
}
List<MetadataField> fieldsWithAuthoritySupport = metadataFieldWithAuthorityRP(context);
for (NameResearcherPage tmpname : names)
{
bindItemsToRP(relationPreferenceService, context,
fieldsWithAuthoritySupport, tmpname, items);
}
}
private static void bindItemsToRP(RelationPreferenceService relationPreferenceService,
Context context, List<MetadataField> fieldsWithAuthoritySupport,
NameResearcherPage tempName, Item[] items) throws BrowseException,
SQLException, AuthorizeException
{
context.turnOffAuthorisationSystem();
Map<String, Integer> cacheCount = new HashMap<String, Integer>();
for (Item item : items)
{
if (tempName.getRejectItems() != null
&& tempName.getRejectItems().contains(item.getID()))
{
log.warn("Item has been reject for this authority - itemID "
+ item.getID());
}
else
{
boolean modified = false;
DCValue[] values = null;
for (MetadataField md : fieldsWithAuthoritySupport)
{
String schema = (MetadataSchema.find(context,
md.getSchemaID())).getName();
values = item.getMetadata(schema, md.getElement(),
md.getQualifier(), Item.ANY);
item.clearMetadata(schema, md.getElement(),
md.getQualifier(), Item.ANY);
for (DCValue value : values)
{
int matches = 0;
if (value.authority == null
&& (value.value.equals(tempName.getName()) || value.value
.startsWith(tempName.getName() + ";")))
{
matches = countNamesMatching(cacheCount,
tempName.getName());
item.addMetadata(
value.schema,
value.element,
value.qualifier,
value.language,
tempName.getName(),
tempName.getPersistentIdentifier(),
matches >= 1 ? Choices.CF_AMBIGUOUS
: matches == 1 ? Choices.CF_UNCERTAIN
: Choices.CF_NOTFOUND);
modified = true;
}
else
{
item.addMetadata(value.schema, value.element,
value.qualifier, value.language,
value.value, value.authority,
value.confidence);
}
}
values = null;
}
if (modified)
{
log.debug("Update item with id " + item.getID());
item.update();
}
context.commit();
context.clearCache();
}
}
context.restoreAuthSystemState();
}
private static int countNamesMatching(Map<String, Integer> cacheCount,
String name)
{
if (cacheCount.containsKey(name))
{
return cacheCount.get(name);
}
ChoiceAuthority ca = (ChoiceAuthority) PluginManager.getNamedPlugin(
ChoiceAuthority.class, RPAuthority.RP_AUTHORITY_NAME);
Choices choices = ca.getBestMatch(null, name, 0, null);
cacheCount.put(name, choices.total);
return choices.total;
}
private static void generatePotentialMatches(Context context,
ResearcherPage researcher) throws SQLException, AuthorizeException,
IOException
{
Set<Integer> ids = getPotentialMatch(context, researcher);
DatabaseManager.updateQuery(context,
"delete from potentialmatches where rp like ?",
researcher.getCrisID());
for (Integer id : ids)
{
TableRow pmTableRow = DatabaseManager.create(context,
"potentialmatches");
pmTableRow.setColumn("rp", researcher.getCrisID());
pmTableRow.setColumn("item_id", id);
DatabaseManager.update(context, pmTableRow);
}
context.commit();
}
public static void generatePotentialMatches(
ApplicationService applicationService, Context context, String rp)
throws SQLException, AuthorizeException, IOException
{
ResearcherPage researcher = applicationService
.getResearcherByAuthorityKey(rp);
if (researcher == null)
{
return;
}
generatePotentialMatches(context, researcher);
}
public static void generatePotentialMatches(ResearcherPage researcher)
{
Context context = null;
try
{
context = new Context();
generatePotentialMatches(context, researcher);
context.complete();
}
catch (Exception e)
{
log.error(e.getMessage(), e);
}
finally
{
if (context != null && context.isValid())
context.abort();
}
}
}
/**
* Support class to build the full list of names to process in the BindItemToRP
* work method
*
* @author cilea
*
*/
class NameResearcherPage
{
/** the name form to lookup for */
private String name;
/** the rp identifier */
private String persistentIdentifier;
private int id;
/** the ids of previous rejected matches */
private Set<Integer> rejectItems;
public NameResearcherPage(String name, String authority, int id,
Set<Integer> rejectItems)
{
this.name = name;
this.persistentIdentifier = authority;
this.id = id;
this.rejectItems = rejectItems;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getPersistentIdentifier()
{
return persistentIdentifier;
}
public void setPersistentIdentifier(String persistentIdentifier)
{
this.persistentIdentifier = persistentIdentifier;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public Set<Integer> getRejectItems()
{
return rejectItems;
}
public void setRejectItems(Set<Integer> rejectItems)
{
this.rejectItems = rejectItems;
}
}