diff --git a/doc/release-notes/215-handles-upgrade-reminder.md b/doc/release-notes/215-handles-upgrade-reminder.md new file mode 100644 index 00000000000..e1cbb8b0d8c --- /dev/null +++ b/doc/release-notes/215-handles-upgrade-reminder.md @@ -0,0 +1,8 @@ +### A reminder for the installations using Handles persistent identifiers (instead of DOIs): remember to upgrade your Handles service installation to a currently supported version. + +Generally, Handles is known to be working reliably even when running older versions that haven't been officially supported in years. We still recommend to check on your service and make sure to upgrade to a supported version (the latest version is 9.3.1, https://www.handle.net/hnr-source/handle-9.3.1-distribution.tar.gz, as of writing this). An older version may be running for you seemingly just fine, but do keep in mind that it may just stop working unexpectedly at any moment, because of some incompatibility introduced in a Java rpm upgrade, or anything similarly unpredictable. + +Handles is also very good about backward incompatibility. Meaning, in most cases you can simply stop the old version, unpack the new version from the distribution and start it on the existing config and database files, and it'll just keep working. However, it is a good idea to keep up with the recommended format upgrades, for the sake of efficiency and to avoid any unexpected surprises, should they finally decide to drop the old database format, for example. The two specific things we recommend: 1) Make sure your service is using a json version of the `siteinfo` bundle (i.e., if you are still using `siteinfo.bin`, convert it to `siteinfo.json` and remove the binary file from the service directory) and 2) Make sure you are using the newer bdbje database format for your handles catalog (i.e., if you still have the files `handles.jdb` and `nas.jdb` in your server directory, convert them to the new format). Follow the simple conversion instructions in the file README.txt in the Handles software distribution. Make sure to stop the service before converting the files and make sure to have a full backup of the existing server directory, just in case). Do not hesitate to contact the Handles support with any questions you may have, as they are very responsive and helpful. + + + diff --git a/doc/release-notes/8674-permalinks.md b/doc/release-notes/8674-permalinks.md new file mode 100644 index 00000000000..5d47b378c91 --- /dev/null +++ b/doc/release-notes/8674-permalinks.md @@ -0,0 +1 @@ +Dataverse now optionally supports PermaLinks, a type of persistent identifier that does not involve a global registry service. PermaLinks are appropriate for Intranet deployment and catalog use cases. diff --git a/doc/sphinx-guides/source/admin/harvestserver.rst b/doc/sphinx-guides/source/admin/harvestserver.rst index 6f4f23fc587..773e048aa76 100644 --- a/doc/sphinx-guides/source/admin/harvestserver.rst +++ b/doc/sphinx-guides/source/admin/harvestserver.rst @@ -18,7 +18,7 @@ If you want to learn more about OAI-PMH, you could take a look at or the `OAI-PMH protocol definition `_. You might consider adding your OAI-enabled Dataverse installation to -`this shared list `_ +`this shared list `_ of such instances. The email portion of :ref:`systemEmail` will be visible via OAI-PMH (from the "Identify" verb). diff --git a/doc/sphinx-guides/source/developers/dev-environment.rst b/doc/sphinx-guides/source/developers/dev-environment.rst index ae6f6db0109..3801dbe76fc 100755 --- a/doc/sphinx-guides/source/developers/dev-environment.rst +++ b/doc/sphinx-guides/source/developers/dev-environment.rst @@ -219,6 +219,8 @@ Run the following command: This will disable DOI registration by using a fake (in-code) DOI provider. Please note that this feature is only available in Dataverse Software 4.10+ and that at present, the UI will give no indication that the DOIs thus minted are fake. +Developers may also wish to consider using :ref:`PermaLinks ` + Configure Your Development Environment for GUI Edits ---------------------------------------------------- diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index cd24a5c974a..1ce07614ce8 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -169,9 +169,15 @@ As the person installing the Dataverse Software, you may or may not be a local m Persistent Identifiers and Publishing Datasets ---------------------------------------------- -Persistent identifiers are a required and integral part of the Dataverse Software. They provide a URL that is guaranteed to resolve to the datasets or files they represent. The Dataverse Software currently supports creating identifiers using DOI and Handle. +Persistent identifiers (PIDs) are a required and integral part of the Dataverse Software. They provide a URL that is guaranteed to resolve to the datasets or files they represent. +The Dataverse Software currently supports creating identifiers using one of several PID providers. +The most appropriate PIDs for public data are DOIs (provided by DataCite or EZID) and Handles. Dataverse also supports PermaLinks which could be useful for intranet or catalog use cases. A Fake "DOI" provider, recommended only for testing and development purposes, also exists. -By default, the installer configures a default DOI namespace (10.5072) with DataCite as the registration provider. Please note that as of the release 4.9.3, we can no longer use EZID as the provider. Unlike EZID, DataCite requires that you register for a test account, configured with your own prefix (please contact support@datacite.org). Once you receive the login name, password, and prefix for the account, configure the credentials in your domain.xml, as the following two JVM options:: +Testing ++++++++ + +By default, the installer configures the DataCite test service as the registration provider. +DataCite requires that you register for a test account, configured with your own prefix (please contact support@datacite.org). Once you receive the login name, password, and prefix for the account, configure the credentials in your domain.xml, as the following two JVM options:: -Ddoi.username=... -Ddoi.password=... @@ -180,11 +186,15 @@ and restart Payara. The prefix can be configured via the API (where it is referr ``curl -X PUT -d 10.xxxx http://localhost:8080/api/admin/settings/:Authority`` +EZID is available to University of California scholars and researchers. Testing can be done using the authority 10.5072 and shoulder FK2 with the "apitest" account (contact EZID for credentials) or an institutional account. Configuration in Dataverse is then analogous to using DataCite; + +The PermaLink and FAKE DOI providers do not involve an external account. PermaLinks are described further below. Use of the FAKE provider is discussed in the :doc:`/developers/dev-environment` section. + Once this is done, you will be able to publish datasets and files, but the persistent identifiers will not be citable, and they will only resolve from the DataCite test environment (and then only if the Dataverse installation from which you published them is accessible - DOIs minted from your laptop will not resolve). Note that any datasets or files created using the test configuration cannot be directly migrated and would need to be created again once a valid DOI namespace is configured. To properly configure persistent identifiers for a production installation, an account and associated namespace must be acquired for a fee from a DOI or HDL provider. **DataCite** (https://www.datacite.org) is the recommended DOI provider (see https://dataversecommunity.global for more on joining DataCite) but **EZID** (http://ezid.cdlib.org) is an option for the University of California according to https://www.cdlib.org/cdlinfo/2017/08/04/ezid-doi-service-is-evolving/ . **Handle.Net** (https://www.handle.net) is the HDL provider. -Once you have your DOI or Handle account credentials and a namespace, configure your Dataverse installation to use them using the JVM options and database settings below. +Once you have a production DOI or Handle account credentials and a namespace, configure your Dataverse installation to use them using the JVM options and database settings below. Configuring Your Dataverse Installation for DOIs ++++++++++++++++++++++++++++++++++++++++++++++++ @@ -232,6 +242,30 @@ Here are the configuration options for handles: Note: If you are **minting your own handles** and plan to set up your own handle service, please refer to `Handle.Net documentation `_. +.. _permalinks: + +Configuring Your Dataverse Installation for PermaLinks +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +PermaLinks are a simple mechanism to provide persistent URLs for datasets and datafiles (if configured) that does not involve an external service providing metadata-based search services. +They are potentially appropriate for Intranet use cases as well as in cases where Dataverse is being used as a catalog or holding duplicate copies of datasets where the authoritative copy already has a DOI or Handle. +PermaLinks use the protocol "perma" (versus "doi" or "handle") and do not use a "/" character as a separator between the authority and shoulder. It is recommended to choose an alphanumeric value for authority that does not resemble that of DOIs (which are primarily numeric and start with "10." as in "10.5072") to avoid PermaLinks being mistaken for DOIs. + +Here are the configuration options for PermaLinks: + +**JVM Options:** + +- :ref:`perma.baseurlstring` + +**Database Settings:** + +- :ref:`:Protocol <:Protocol>` +- :ref:`:Authority <:Authority>` +- :ref:`:Shoulder <:Shoulder>` +- :ref:`:IdentifierGenerationStyle <:IdentifierGenerationStyle>` (optional) +- :ref:`:DataFilePIDFormat <:DataFilePIDFormat>` (optional) +- :ref:`:FilePIDsEnabled <:FilePIDsEnabled>` (optional, defaults to true) + .. _auth-modes: Auth Modes: Local vs. Remote vs. Both @@ -2098,7 +2132,23 @@ Can also be set via any `supported MicroProfile Config API source`_, e.g. the en **WARNING:** For security, do not use the sources "environment variable" or "system property" (JVM option) in a production context! Rely on password alias, secrets directory or cloud based sources instead! +.. _perma.baseurlstring: + +perma.baseurlstring ++++++++++++++++++++ + +When using :ref:`PermaLinks `, perma.baseurlstring can be used to configure an external resolver. Dataverse will associate a PermaLink PID with the URL: +``/citation?persistentId=perma:``. The default value is your Dataverse site URL, which will result in PermaLinks correctly resolving to the appropriate dataset page. + +To set this option, issue a command such as: + +``./asadmin create-jvm-options '-Dperma.baseurlstring=https\://localresolver.yourdataverse.org'`` + +See also these related database settings: +- :ref:`:Protocol` +- :ref:`:Authority` +- :ref:`:Shoulder` .. _feature-flags: @@ -2287,7 +2337,7 @@ By default the footer says "Copyright © [YYYY]" but you can add text after the :DoiProvider ++++++++++++ -As of this writing "DataCite" and "EZID" are the only valid options for production installations. Developers using Dataverse Software 4.10+ are welcome to use the keyword "FAKE" to configure a non-production installation with an non-resolving, in-code provider, which will basically short-circuit the DOI publishing process. ``:DoiProvider`` is only needed if you are using DOI. +As of this writing "DataCite", and "EZID" are the only valid options for production installations. Developers using Dataverse Software 4.10+ are welcome to use the keyword "FAKE" to configure a non-production installation with an non-resolving, in-code provider, which will basically short-circuit the DOI publishing process. ``:DoiProvider`` is only needed if you are using DOI. ``curl -X PUT -d DataCite http://localhost:8080/api/admin/settings/:DoiProvider`` @@ -2302,7 +2352,7 @@ This setting relates to the ``:Protocol``, ``:Authority``, ``:Shoulder``, and `` :Protocol +++++++++ -As of this writing "doi" and "hdl" are the only valid option for the protocol for a persistent ID. +As of this writing "doi","hdl", and "perma" are the only valid option for the protocol for a persistent ID. ``curl -X PUT -d doi http://localhost:8080/api/admin/settings/:Protocol`` @@ -2311,9 +2361,9 @@ As of this writing "doi" and "hdl" are the only valid option for the protocol fo :Authority ++++++++++ -Use the authority assigned to you by your DoiProvider or HandleProvider. +Use the authority assigned to you by your DoiProvider or HandleProvider, or your choice if using PermaLinks. -Please note that the authority cannot have a slash ("/") in it. +Please note that a DOI or Handle authority cannot have a slash ("/") in it (slash is also not recommended for PermaLink authorities). ``curl -X PUT -d 10.xxxx http://localhost:8080/api/admin/settings/:Authority`` @@ -2322,7 +2372,7 @@ Please note that the authority cannot have a slash ("/") in it. :Shoulder +++++++++ -Out of the box, the DOI shoulder is set to "FK2/" but this is for testing only! When you apply for your DOI namespace, you may have requested a shoulder. The following is only an example and a trailing slash is optional. +The shoulder is used with DOIs and PermaLinks. Out of the box, the shoulder is set to "FK2/" but this is for testing only! When you apply for your DOI authority/namespace, you may have been assigned a shoulder. The following is only an example and a trailing slash is optional. ``curl -X PUT -d "MyShoulder/" http://localhost:8080/api/admin/settings/:Shoulder`` diff --git a/src/main/java/edu/harvard/iq/dataverse/AbstractGlobalIdServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/AbstractGlobalIdServiceBean.java index f6cbd01ece0..550b6cbc506 100644 --- a/src/main/java/edu/harvard/iq/dataverse/AbstractGlobalIdServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/AbstractGlobalIdServiceBean.java @@ -3,11 +3,13 @@ import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.SystemConfig; import java.io.InputStream; - import javax.ejb.EJB; +import javax.inject.Inject; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; + +import org.apache.commons.lang3.RandomStringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -17,27 +19,21 @@ public abstract class AbstractGlobalIdServiceBean implements GlobalIdServiceBean private static final Logger logger = Logger.getLogger(AbstractGlobalIdServiceBean.class.getCanonicalName()); - @EJB + @Inject DataverseServiceBean dataverseService; @EJB + protected SettingsServiceBean settingsService; - @EJB - EjbDataverseEngine commandEngine; - @EJB - DatasetServiceBean datasetService; - @EJB - DataFileServiceBean datafileService; - @EJB + @Inject + protected + DvObjectServiceBean dvObjectService; + @Inject SystemConfig systemConfig; + + protected boolean configured = false; public static String UNAVAILABLE = ":unav"; - @Override - public String getIdentifierForLookup(String protocol, String authority, String identifier) { - logger.log(Level.FINE,"getIdentifierForLookup"); - return protocol + ":" + authority + "/" + identifier; - } - @Override public Map getMetadataForCreateIndicator(DvObject dvObjectIn) { logger.log(Level.FINE,"getMetadataForCreateIndicator(DvObject)"); @@ -101,14 +97,10 @@ protected String getTargetUrl(DvObject dvObjectIn) { @Override public String getIdentifier(DvObject dvObject) { - return dvObject.getGlobalId().asString(); + GlobalId gid = dvObject.getGlobalId(); + return gid != null ? gid.asString() : null; } - protected String getTargetUrl(Dataset datasetIn) { - logger.log(Level.FINE,"getTargetUrl"); - return systemConfig.getDataverseSiteUrl() + Dataset.TARGET_URL + datasetIn.getGlobalIdString(); - } - protected String generateYear (DvObject dvObjectIn){ return dvObjectIn.getYearPublishedCreated(); } @@ -120,16 +112,39 @@ public Map getMetadataForTargetURL(DvObject dvObject) { return metadata; } + @Override + public boolean alreadyExists(DvObject dvo) throws Exception { + if(dvo==null) { + logger.severe("Null DvObject sent to alreadyExists()."); + return false; + } + GlobalId globalId = dvo.getGlobalId(); + if(globalId == null) { + return false; + } + return alreadyExists(globalId); + } + + /* + * ToDo: the DvObject being sent in provides partial support for the case where + * it has a different authority/protocol than what is configured (i.e. a legacy + * Pid that can actually be updated by the Pid account being used.) Removing + * this now would potentially break/make it harder to handle that case prior to + * support for configuring multiple Pid providers. Once that exists, it would be + * cleaner to always find the PidProvider associated with the + * protocol/authority/shoulder of the current dataset and then not pass the + * DvObject as a param. (This would also remove calls to get the settings since + * that would be done at construction.) + */ @Override public DvObject generateIdentifier(DvObject dvObject) { String protocol = dvObject.getProtocol() == null ? settingsService.getValueForKey(SettingsServiceBean.Key.Protocol) : dvObject.getProtocol(); String authority = dvObject.getAuthority() == null ? settingsService.getValueForKey(SettingsServiceBean.Key.Authority) : dvObject.getAuthority(); - GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(protocol, commandEngine.getContext()); if (dvObject.isInstanceofDataset()) { - dvObject.setIdentifier(datasetService.generateDatasetIdentifier((Dataset) dvObject, idServiceBean)); + dvObject.setIdentifier(generateDatasetIdentifier((Dataset) dvObject)); } else { - dvObject.setIdentifier(datafileService.generateDataFileIdentifier((DataFile) dvObject, idServiceBean)); + dvObject.setIdentifier(generateDataFileIdentifier((DataFile) dvObject)); } if (dvObject.getProtocol() == null) { dvObject.setProtocol(protocol); @@ -140,6 +155,227 @@ public DvObject generateIdentifier(DvObject dvObject) { return dvObject; } + //ToDo just send the DvObject.DType + public String generateDatasetIdentifier(Dataset dataset) { + //ToDo - track these in the bean + String identifierType = settingsService.getValueForKey(SettingsServiceBean.Key.IdentifierGenerationStyle, "randomString"); + String shoulder = settingsService.getValueForKey(SettingsServiceBean.Key.Shoulder, ""); + + switch (identifierType) { + case "randomString": + return generateIdentifierAsRandomString(dataset, shoulder); + case "storedProcGenerated": + return generateIdentifierFromStoredProcedureIndependent(dataset, shoulder); + default: + /* Should we throw an exception instead?? -- L.A. 4.6.2 */ + return generateIdentifierAsRandomString(dataset, shoulder); + } + } + + + /** + * Check that a identifier entered by the user is unique (not currently used + * for any other study in this Dataverse Network) also check for duplicate + * in EZID if needed + * @param userIdentifier + * @param dataset + * @return {@code true} if the identifier is unique, {@code false} otherwise. + */ + public boolean isGlobalIdUnique(GlobalId globalId) { + if ( ! dvObjectService.isGlobalIdLocallyUnique(globalId) ) { + return false; // duplication found in local database + } + + // not in local DB, look in the persistent identifier service + try { + return ! alreadyExists(globalId); + } catch (Exception e){ + //we can live with failure - means identifier not found remotely + } + + return true; + } + + /** + * Parse a Persistent Id and set the protocol, authority, and identifier + * + * Example 1: doi:10.5072/FK2/BYM3IW + * protocol: doi + * authority: 10.5072 + * identifier: FK2/BYM3IW + * + * Example 2: hdl:1902.1/111012 + * protocol: hdl + * authority: 1902.1 + * identifier: 111012 + * + * @param identifierString + * @param separator the string that separates the authority from the identifier. + * @param destination the global id that will contain the parsed data. + * @return {@code destination}, after its fields have been updated, or + * {@code null} if parsing failed. + */ + @Override + public GlobalId parsePersistentId(String fullIdentifierString) { + if(!configured) { + return null; + } + int index1 = fullIdentifierString.indexOf(':'); + if (index1 > 0) { // ':' found with one or more characters before it + String protocol = fullIdentifierString.substring(0, index1); + GlobalId globalId = parsePersistentId(protocol, fullIdentifierString.substring(index1+1)); + return globalId; + } + logger.log(Level.INFO, "Error parsing identifier: {0}: '':'' not found in string", fullIdentifierString); + return null; + } + + protected GlobalId parsePersistentId(String protocol, String identifierString) { + if(!configured) { + return null; + } + String authority; + String identifier; + if (identifierString == null) { + return null; + } + int index = identifierString.indexOf('/'); + if (index > 0 && (index + 1) < identifierString.length()) { + // '/' found with one or more characters + // before and after it + // Strip any whitespace, ; and ' from authority (should finding them cause a + // failure instead?) + authority = GlobalIdServiceBean.formatIdentifierString(identifierString.substring(0, index)); + if (GlobalIdServiceBean.testforNullTerminator(authority)) { + return null; + } + identifier = GlobalIdServiceBean.formatIdentifierString(identifierString.substring(index + 1)); + if (GlobalIdServiceBean.testforNullTerminator(identifier)) { + return null; + } + } else { + logger.log(Level.INFO, "Error parsing identifier: {0}: '':/'' not found in string", + identifierString); + return null; + } + return parsePersistentId(protocol, authority, identifier); + } + + public GlobalId parsePersistentId(String protocol, String authority, String identifier) { + if(!configured) { + return null; + } + logger.fine("Parsing: " + protocol + ":" + authority + getSeparator() + identifier + " in " + getProviderInformation().get(0)); + if(!GlobalIdServiceBean.isValidGlobalId(protocol, authority, identifier)) { + return null; + } + return new GlobalId(protocol, authority, identifier, getSeparator(), getUrlPrefix(), + getProviderInformation().get(0)); + } + + + public String getSeparator() { + //The standard default + return "/"; + } + + @Override + public String generateDataFileIdentifier(DataFile datafile) { + String doiIdentifierType = settingsService.getValueForKey(SettingsServiceBean.Key.IdentifierGenerationStyle, "randomString"); + String doiDataFileFormat = settingsService.getValueForKey(SettingsServiceBean.Key.DataFilePIDFormat, SystemConfig.DataFilePIDFormat.DEPENDENT.toString()); + + String prepend = ""; + if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.DEPENDENT.toString())){ + //If format is dependent then pre-pend the dataset identifier + prepend = datafile.getOwner().getIdentifier() + "/"; + datafile.setProtocol(datafile.getOwner().getProtocol()); + datafile.setAuthority(datafile.getOwner().getAuthority()); + } else { + //If there's a shoulder prepend independent identifiers with it + prepend = settingsService.getValueForKey(SettingsServiceBean.Key.Shoulder, ""); + datafile.setProtocol(settingsService.getValueForKey(SettingsServiceBean.Key.Protocol)); + datafile.setAuthority(settingsService.getValueForKey(SettingsServiceBean.Key.Authority)); + } + + switch (doiIdentifierType) { + case "randomString": + return generateIdentifierAsRandomString(datafile, prepend); + case "storedProcGenerated": + if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.INDEPENDENT.toString())){ + return generateIdentifierFromStoredProcedureIndependent(datafile, prepend); + } else { + return generateIdentifierFromStoredProcedureDependent(datafile, prepend); + } + default: + /* Should we throw an exception instead?? -- L.A. 4.6.2 */ + return generateIdentifierAsRandomString(datafile, prepend); + } + } + + + /* + * This method checks locally for a DvObject with the same PID and if that is OK, checks with the PID service. + * @param dvo - the object to check (ToDo - get protocol/authority from this PidProvider object) + * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it could be the shoulder or the parent Dataset identifier + */ + private String generateIdentifierAsRandomString(DvObject dvo, String prepend) { + String identifier = null; + do { + identifier = prepend + RandomStringUtils.randomAlphanumeric(6).toUpperCase(); + } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(), this.getUrlPrefix(), this.getProviderInformation().get(0)))); + + return identifier; + } + + /* + * This method checks locally for a DvObject with the same PID and if that is OK, checks with the PID service. + * @param dvo - the object to check (ToDo - get protocol/authority from this PidProvider object) + * @param prepend - for Datasets, this is always the shoulder, for DataFiles, it could be the shoulder or the parent Dataset identifier + */ + + private String generateIdentifierFromStoredProcedureIndependent(DvObject dvo, String prepend) { + String identifier; + do { + String identifierFromStoredProcedure = dvObjectService.generateNewIdentifierByStoredProcedure(); + // some diagnostics here maybe - is it possible to determine that it's failing + // because the stored procedure hasn't been created in the database? + if (identifierFromStoredProcedure == null) { + return null; + } + identifier = prepend + identifierFromStoredProcedure; + } while (!isGlobalIdUnique(new GlobalId(dvo.getProtocol(), dvo.getAuthority(), identifier, this.getSeparator(), this.getUrlPrefix(), this.getProviderInformation().get(0)))); + + return identifier; + } + + /*This method is only used for DataFiles with DEPENDENT Pids. It is not for Datasets + * + */ + private String generateIdentifierFromStoredProcedureDependent(DataFile datafile, String prepend) { + String identifier; + Long retVal; + retVal = Long.valueOf(0L); + //ToDo - replace loops with one lookup for largest entry? (the do loop runs ~n**2/2 calls). The check for existingIdentifiers means this is mostly a local loop now, versus involving db or PidProvider calls, but still...) + + // This will catch identifiers already assigned in the current transaction (e.g. + // in FinalizeDatasetPublicationCommand) that haven't been committed to the db + // without having to make a call to the PIDProvider + Set existingIdentifiers = new HashSet(); + List files = datafile.getOwner().getFiles(); + for(DataFile f:files) { + existingIdentifiers.add(f.getIdentifier()); + } + + do { + retVal++; + identifier = prepend + retVal.toString(); + + } while (existingIdentifiers.contains(identifier) || !isGlobalIdUnique(new GlobalId(datafile.getProtocol(), datafile.getAuthority(), identifier, this.getSeparator(), this.getUrlPrefix(), this.getProviderInformation().get(0)))); + + return identifier; + } + + class GlobalIdMetadataTemplate { @@ -159,7 +395,6 @@ public GlobalIdMetadataTemplate(){ private String xmlMetadata; private String identifier; - private String datasetIdentifier; private List datafileIdentifiers; private List creators; private String title; @@ -245,7 +480,7 @@ public String generateXML(DvObject dvObject) { // Added to prevent a NullPointerException when trying to destroy datasets when using DataCite rather than EZID. publisherYearFinal = this.publisherYear; } - xmlMetadata = template.replace("${identifier}", this.identifier.trim()) + xmlMetadata = template.replace("${identifier}", getIdentifier().trim()) .replace("${title}", this.title) .replace("${publisher}", this.publisher) .replace("${publisherYear}", publisherYearFinal) @@ -371,10 +606,6 @@ public void setIdentifier(String identifier) { this.identifier = identifier; } - public void setDatasetIdentifier(String datasetIdentifier) { - this.datasetIdentifier = datasetIdentifier; - } - public List getCreators() { return creators; } @@ -428,10 +659,6 @@ public String getMetadataFromDvObject(String identifier, Map met DataFile df = (DataFile) dvObject; String fileDescription = df.getDescription(); metadataTemplate.setDescription(fileDescription == null ? "" : fileDescription); - String datasetPid = df.getOwner().getGlobalId().asString(); - metadataTemplate.setDatasetIdentifier(datasetPid); - } else { - metadataTemplate.setDatasetIdentifier(""); } metadataTemplate.setContacts(dataset.getLatestVersion().getDatasetContacts()); @@ -448,5 +675,15 @@ public String getMetadataFromDvObject(String identifier, Map met logger.log(Level.FINE, "XML to send to DataCite: {0}", xmlMetadata); return xmlMetadata; } + + @Override + public boolean canManagePID() { + //The default expectation is that PID providers are configured to manage some set (i.e. based on protocol/authority/shoulder) of PIDs + return true; + } + @Override + public boolean isConfigured() { + return configured; + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/CitationServlet.java b/src/main/java/edu/harvard/iq/dataverse/CitationServlet.java index 2b342b09610..f6b4e3dc99a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/CitationServlet.java +++ b/src/main/java/edu/harvard/iq/dataverse/CitationServlet.java @@ -5,6 +5,7 @@ */ package edu.harvard.iq.dataverse; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.util.StringUtil; import java.io.IOException; import java.io.PrintWriter; @@ -21,7 +22,7 @@ public class CitationServlet extends HttpServlet { @EJB - DatasetServiceBean datasetService; + DvObjectServiceBean dvObjectService; /** * Processes requests for both HTTP GET and POST @@ -37,10 +38,14 @@ protected void processRequest(HttpServletRequest request, HttpServletResponse re String persistentId = request.getParameter("persistentId"); if (persistentId != null) { - Dataset ds = datasetService.findByGlobalId(persistentId); - if (ds != null) { - response.sendRedirect("dataset.xhtml?persistentId=" + persistentId); - return; + DvObject dob = dvObjectService.findByGlobalId(PidUtil.parseAsGlobalID(persistentId)); + if (dob != null) { + if (dob instanceof Dataset) { + response.sendRedirect("dataset.xhtml?persistentId=" + persistentId); + } else if (dob instanceof DataFile) { + response.sendRedirect("file.xhtml?persistentId=" + persistentId); + } + return; } } response.sendError(HttpServletResponse.SC_NOT_FOUND); diff --git a/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteRegisterService.java b/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteRegisterService.java index 218e4c85474..809cedf50f5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteRegisterService.java +++ b/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteRegisterService.java @@ -546,7 +546,7 @@ private String generateRelatedIdentifiers(DvObject dvObject) { datafileIdentifiers = new ArrayList<>(); for (DataFile dataFile : dataset.getFiles()) { - if (!dataFile.getGlobalId().asString().isEmpty()) { + if (dataFile.getGlobalId() != null) { if (sb.toString().isEmpty()) { sb.append(""); } diff --git a/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteServiceBean.java index e7dd49a6926..a6366c2b64b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DOIDataCiteServiceBean.java @@ -10,19 +10,23 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.ejb.Stateless; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpStatus; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key; + /** * * @author luopc */ @Stateless -public class DOIDataCiteServiceBean extends AbstractGlobalIdServiceBean { +public class DOIDataCiteServiceBean extends DOIServiceBean { private static final Logger logger = Logger.getLogger(DOIDataCiteServiceBean.class.getCanonicalName()); @@ -34,7 +38,12 @@ public class DOIDataCiteServiceBean extends AbstractGlobalIdServiceBean { @EJB DOIDataCiteRegisterService doiDataCiteRegisterService; - public DOIDataCiteServiceBean() { + @PostConstruct + private void init() { + String doiProvider = settingsService.getValueForKey(Key.DoiProvider, ""); + if("DataCite".equals(doiProvider)) { + configured=true; + } } @Override @@ -42,14 +51,7 @@ public boolean registerWhenPublished() { return false; } - @Override - public boolean alreadyExists(DvObject dvObject) { - if(dvObject==null) { - logger.severe("Null DvObject sent to alreadyExists()."); - return false; - } - return alreadyExists(dvObject.getGlobalId()); - } + @Override public boolean alreadyExists(GlobalId pid) { @@ -90,10 +92,10 @@ public String createIdentifier(DvObject dvObject) throws Exception { } @Override - public HashMap getIdentifierMetadata(DvObject dvObject) { + public Map getIdentifierMetadata(DvObject dvObject) { logger.log(Level.FINE,"getIdentifierMetadata"); String identifier = getIdentifier(dvObject); - HashMap metadata = new HashMap<>(); + Map metadata = new HashMap<>(); try { metadata = doiDataCiteRegisterService.getMetadata(identifier); } catch (Exception e) { @@ -103,29 +105,6 @@ public HashMap getIdentifierMetadata(DvObject dvObject) { } - /** - * Looks up the metadata for a Global Identifier - * @param protocol the identifier system, e.g. "doi" - * @param authority the namespace that the authority manages in the identifier system - * @param identifier the local identifier part - * @return a Map of metadata. It is empty when the lookup failed, e.g. when - * the identifier does not exist. - */ - @Override - public HashMap lookupMetadataFromIdentifier(String protocol, String authority, String identifier) { - logger.log(Level.FINE,"lookupMetadataFromIdentifier"); - String identifierOut = getIdentifierForLookup(protocol, authority, identifier); - HashMap metadata = new HashMap<>(); - try { - metadata = doiDataCiteRegisterService.getMetadata(identifierOut); - } catch (Exception e) { - logger.log(Level.WARNING, "None existing so we can use this identifier"); - logger.log(Level.WARNING, "identifier: {0}", identifierOut); - } - return metadata; - } - - /** * Modifies the DOI metadata for a Dataset * @param dvObject the dvObject whose metadata needs to be modified @@ -277,5 +256,7 @@ public List getProviderInformation(){ return providerInfo; } + //PID recognition + } diff --git a/src/main/java/edu/harvard/iq/dataverse/DOIEZIdServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DOIEZIdServiceBean.java index d21caf32411..d758401b952 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DOIEZIdServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DOIEZIdServiceBean.java @@ -1,11 +1,13 @@ package edu.harvard.iq.dataverse; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key; import edu.ucsb.nceas.ezid.EZIDException; import edu.ucsb.nceas.ezid.EZIDService; import edu.ucsb.nceas.ezid.EZIDServiceRequest; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; + import javax.ejb.Stateless; /** @@ -13,7 +15,7 @@ * @author skraffmiller */ @Stateless -public class DOIEZIdServiceBean extends AbstractGlobalIdServiceBean { +public class DOIEZIdServiceBean extends DOIServiceBean { EZIDService ezidService; EZIDServiceRequest ezidServiceRequest; @@ -23,22 +25,30 @@ public class DOIEZIdServiceBean extends AbstractGlobalIdServiceBean { // get username and password from system properties private String USERNAME = ""; private String PASSWORD = ""; - public DOIEZIdServiceBean() { logger.log(Level.FINE,"Constructor"); - baseURLString = System.getProperty("doi.baseurlstring"); - ezidService = new EZIDService(baseURLString); - USERNAME = System.getProperty("doi.username"); - PASSWORD = System.getProperty("doi.password"); - logger.log(Level.FINE, "Using baseURLString {0}", baseURLString); try { + // Guessing these are System.getProperty rather than using settingsService + // because this is a constructor rather than a @PostConstruct method + baseURLString = System.getProperty("doi.baseurlstring"); + logger.log(Level.FINE, "Using baseURLString {0}", baseURLString); + ezidService = new EZIDService(baseURLString); + USERNAME = System.getProperty("doi.username"); + PASSWORD = System.getProperty("doi.password"); ezidService.login(USERNAME, PASSWORD); + configured = true; } catch (EZIDException e) { - logger.log(Level.WARNING, "login failed "); - logger.log(Level.WARNING, "Exception String: {0}", e.toString()); - logger.log(Level.WARNING, "localized message: {0}", e.getLocalizedMessage()); - logger.log(Level.WARNING, "cause: ", e.getCause()); - logger.log(Level.WARNING, "message {0}", e.getMessage()); + // As a stateless bean, this constructor runs even when we're not using ezid, so + // lowering the log level in that case + // (Future work is expected to change PidProviders from being stateless beans + // going forward and at that point we won't be initializing this unless it's + // configured. + Level level = (baseURLString.contains("ezid")) ? Level.WARNING : Level.FINE; + logger.log(level, "login failed "); + logger.log(level, "Exception String: {0}", e.toString()); + logger.log(level, "localized message: {0}", e.getLocalizedMessage()); + logger.log(level, "cause: ", e.getCause()); + logger.log(level, "message {0}", e.getMessage()); } catch (Exception e) { logger.log(Level.SEVERE, "Other Error on ezidService.login(USERNAME, PASSWORD) - not EZIDException ", e.getMessage()); } @@ -102,32 +112,6 @@ public Map getIdentifierMetadata(DvObject dvObject) { return metadata; } - /** - * Looks up the metadata for a Global Identifier - * - * @param protocol the identifier system, e.g. "doi" - * @param authority the namespace that the authority manages in the - * identifier system - * identifier part - * @param identifier the local identifier part - * @return a Map of metadata. It is empty when the lookup failed, e.g. when - * the identifier does not exist. - */ - @Override - public HashMap lookupMetadataFromIdentifier(String protocol, String authority, String identifier) { - logger.log(Level.FINE,"lookupMetadataFromIdentifier"); - String identifierOut = getIdentifierForLookup(protocol, authority, identifier); - HashMap metadata = new HashMap<>(); - try { - metadata = ezidService.getMetadata(identifierOut); - } catch (EZIDException e) { - logger.log(Level.FINE, "None existing so we can use this identifier"); - logger.log(Level.FINE, "identifier: {0}", identifierOut); - return metadata; - } - return metadata; - } - /** * Modifies the EZID metadata for a Dataset * diff --git a/src/main/java/edu/harvard/iq/dataverse/DOIServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DOIServiceBean.java new file mode 100644 index 00000000000..7ac84b9dc70 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/DOIServiceBean.java @@ -0,0 +1,56 @@ +package edu.harvard.iq.dataverse; + +public abstract class DOIServiceBean extends AbstractGlobalIdServiceBean { + + public static final String DOI_PROTOCOL = "doi"; + public static final String DOI_RESOLVER_URL = "https://doi.org/"; + public static final String HTTP_DOI_RESOLVER_URL = "http://doi.org/"; + public static final String DXDOI_RESOLVER_URL = "https://dx.doi.org/"; + public static final String HTTP_DXDOI_RESOLVER_URL = "http://dx.doi.org/"; + + public DOIServiceBean() { + super(); + } + + @Override + public GlobalId parsePersistentId(String pidString) { + if (pidString.startsWith(DOI_RESOLVER_URL)) { + pidString = pidString.replace(DOI_RESOLVER_URL, + (DOI_PROTOCOL + ":")); + } else if (pidString.startsWith(HTTP_DOI_RESOLVER_URL)) { + pidString = pidString.replace(HTTP_DOI_RESOLVER_URL, + (DOI_PROTOCOL + ":")); + } else if (pidString.startsWith(DXDOI_RESOLVER_URL)) { + pidString = pidString.replace(DXDOI_RESOLVER_URL, + (DOI_PROTOCOL + ":")); + } + return super.parsePersistentId(pidString); + } + + @Override + public GlobalId parsePersistentId(String protocol, String identifierString) { + + if (!DOI_PROTOCOL.equals(protocol)) { + return null; + } + GlobalId globalId = super.parsePersistentId(protocol, identifierString); + if (globalId!=null && !GlobalIdServiceBean.checkDOIAuthority(globalId.getAuthority())) { + return null; + } + return globalId; + } + + @Override + public GlobalId parsePersistentId(String protocol, String authority, String identifier) { + + if (!DOI_PROTOCOL.equals(protocol)) { + return null; + } + return super.parsePersistentId(protocol, authority, identifier); + } + + public String getUrlPrefix() { + return DOI_RESOLVER_URL; + } + +} \ No newline at end of file diff --git a/src/main/java/edu/harvard/iq/dataverse/DataCitation.java b/src/main/java/edu/harvard/iq/dataverse/DataCitation.java index abe3cc3e6d7..e84d7fc487c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataCitation.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataCitation.java @@ -207,7 +207,7 @@ public String toString(boolean html, boolean anonymized) { if (persistentId != null) { // always show url format - citationList.add(formatURL(persistentId.toURL().toString(), persistentId.toURL().toString(), html)); + citationList.add(formatURL(persistentId.asURL(), persistentId.asURL(), html)); } citationList.add(formatString(publisher, html)); citationList.add(version); @@ -298,7 +298,7 @@ public void writeAsBibtexCitation(OutputStream os) throws IOException { out.write(persistentId.getIdentifier()); out.write("},\r\n"); out.write("url = {"); - out.write(persistentId.toURL().toString()); + out.write(persistentId.asURL()); out.write("}\r\n"); out.write("}\r\n"); out.flush(); @@ -387,7 +387,7 @@ public void writeAsRISCitation(OutputStream os) throws IOException { out.write("SE - " + date + "\r\n"); - out.write("UR - " + persistentId.toURL().toString() + "\r\n"); + out.write("UR - " + persistentId.asURL() + "\r\n"); out.write("PB - " + publisher + "\r\n"); // a DataFile citation also includes filename und UNF, if applicable: @@ -584,7 +584,7 @@ private void createEndNoteXML(XMLStreamWriter xmlw) throws XMLStreamException { xmlw.writeStartElement("urls"); xmlw.writeStartElement("related-urls"); xmlw.writeStartElement("url"); - xmlw.writeCharacters(getPersistentId().toURL().toString()); + xmlw.writeCharacters(getPersistentId().asURL()); xmlw.writeEndElement(); // url xmlw.writeEndElement(); // related-urls xmlw.writeEndElement(); // urls @@ -781,18 +781,13 @@ private GlobalId getPIDFrom(DatasetVersion dsv, DvObject dv) { || HarvestingClient.HARVEST_STYLE_ICPSR.equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle()) || HarvestingClient.HARVEST_STYLE_DATAVERSE .equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle())) { - // creating a global id like this: - // persistentId = new GlobalId(dv.getGlobalId()); - // you end up doing new GlobalId((New GlobalId(dv)).toString()) - // - doing an extra formatting-and-parsing-again - // This achieves the same thing: if(!isDirect()) { if (!StringUtils.isEmpty(dsv.getDataset().getIdentifier())) { - return new GlobalId(dsv.getDataset()); + return dsv.getDataset().getGlobalId(); } } else { if (!StringUtils.isEmpty(dv.getIdentifier())) { - return new GlobalId(dv); + return dv.getGlobalId(); } } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DataFile.java b/src/main/java/edu/harvard/iq/dataverse/DataFile.java index 5171e8d49f2..372cb872c46 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataFile.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataFile.java @@ -956,7 +956,7 @@ public JsonObject asGsonObject(boolean prettyPrint){ // https://github.com/IQSS/dataverse/issues/761, https://github.com/IQSS/dataverse/issues/2110, https://github.com/IQSS/dataverse/issues/3191 // datasetMap.put("title", thisFileMetadata.getDatasetVersion().getTitle()); - datasetMap.put("persistentId", getOwner().getGlobalIdString()); + datasetMap.put("persistentId", getOwner().getGlobalId().asString()); datasetMap.put("url", getOwner().getPersistentURL()); datasetMap.put("version", thisFileMetadata.getDatasetVersion().getSemanticVersion()); datasetMap.put("id", getOwner().getId()); @@ -1034,6 +1034,10 @@ public String getCreateDateFormattedYYYYMMDD() { return null; } + @Override + public String getTargetUrl() { + return DataFile.TARGET_URL; + } } // end of class diff --git a/src/main/java/edu/harvard/iq/dataverse/DataFileServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataFileServiceBean.java index 7da06f36be4..865cc4c4593 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataFileServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataFileServiceBean.java @@ -73,7 +73,7 @@ public class DataFileServiceBean implements java.io.Serializable { // Assorted useful mime types: // 3rd-party and/or proprietary tabular data formasts that we know - // how to ingest: + // how to ingest: private static final String MIME_TYPE_STATA = "application/x-stata"; private static final String MIME_TYPE_STATA13 = "application/x-stata-13"; @@ -155,7 +155,7 @@ public DataFile find(Object pk) { }*/ public DataFile findByGlobalId(String globalId) { - return (DataFile) dvObjectService.findByGlobalId(globalId, DataFile.DATAFILE_DTYPE_STRING); + return (DataFile) dvObjectService.findByGlobalId(globalId, DvObject.DType.DataFile); } public List findByCreatorId(Long creatorId) { @@ -357,7 +357,7 @@ public DataFile findCheapAndEasy(Long id) { Object[] result; try { - result = (Object[]) em.createNativeQuery("SELECT t0.ID, t0.CREATEDATE, t0.INDEXTIME, t0.MODIFICATIONTIME, t0.PERMISSIONINDEXTIME, t0.PERMISSIONMODIFICATIONTIME, t0.PUBLICATIONDATE, t0.CREATOR_ID, t0.RELEASEUSER_ID, t0.PREVIEWIMAGEAVAILABLE, t1.CONTENTTYPE, t0.STORAGEIDENTIFIER, t1.FILESIZE, t1.INGESTSTATUS, t1.CHECKSUMVALUE, t1.RESTRICTED, t3.ID, t2.AUTHORITY, t2.IDENTIFIER, t1.CHECKSUMTYPE, t1.PREVIOUSDATAFILEID, t1.ROOTDATAFILEID, t0.AUTHORITY, T0.PROTOCOL, T0.IDENTIFIER FROM DVOBJECT t0, DATAFILE t1, DVOBJECT t2, DATASET t3 WHERE ((t0.ID = " + id + ") AND (t0.OWNER_ID = t2.ID) AND (t2.ID = t3.ID) AND (t1.ID = t0.ID))").getSingleResult(); + result = (Object[]) em.createNativeQuery("SELECT t0.ID, t0.CREATEDATE, t0.INDEXTIME, t0.MODIFICATIONTIME, t0.PERMISSIONINDEXTIME, t0.PERMISSIONMODIFICATIONTIME, t0.PUBLICATIONDATE, t0.CREATOR_ID, t0.RELEASEUSER_ID, t0.PREVIEWIMAGEAVAILABLE, t1.CONTENTTYPE, t0.STORAGEIDENTIFIER, t1.FILESIZE, t1.INGESTSTATUS, t1.CHECKSUMVALUE, t1.RESTRICTED, t3.ID, t2.AUTHORITY, t2.IDENTIFIER, t1.CHECKSUMTYPE, t1.PREVIOUSDATAFILEID, t1.ROOTDATAFILEID, t0.AUTHORITY, T0.PROTOCOL, T0.IDENTIFIER, t2.PROTOCOL FROM DVOBJECT t0, DATAFILE t1, DVOBJECT t2, DATASET t3 WHERE ((t0.ID = " + id + ") AND (t0.OWNER_ID = t2.ID) AND (t2.ID = t3.ID) AND (t1.ID = t0.ID))").getSingleResult(); } catch (Exception ex) { return null; } @@ -501,7 +501,9 @@ public DataFile findCheapAndEasy(Long id) { if (identifier != null) { dataFile.setIdentifier(identifier); } - + + owner.setProtocol((String) result[25]); + dataFile.setOwner(owner); // If content type indicates it's tabular data, spend 2 extra queries @@ -1427,75 +1429,6 @@ public List selectFilesWithMissingOriginalSizes() { } } - public String generateDataFileIdentifier(DataFile datafile, GlobalIdServiceBean idServiceBean) { - String doiIdentifierType = settingsService.getValueForKey(SettingsServiceBean.Key.IdentifierGenerationStyle, "randomString"); - String doiDataFileFormat = settingsService.getValueForKey(SettingsServiceBean.Key.DataFilePIDFormat, "DEPENDENT"); - - String prepend = ""; - if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.DEPENDENT.toString())){ - //If format is dependent then pre-pend the dataset identifier - prepend = datafile.getOwner().getIdentifier() + "/"; - } else { - //If there's a shoulder prepend independent identifiers with it - prepend = settingsService.getValueForKey(SettingsServiceBean.Key.Shoulder, ""); - } - - switch (doiIdentifierType) { - case "randomString": - return generateIdentifierAsRandomString(datafile, idServiceBean, prepend); - case "storedProcGenerated": - if (doiDataFileFormat.equals(SystemConfig.DataFilePIDFormat.INDEPENDENT.toString())){ - return generateIdentifierFromStoredProcedureIndependent(datafile, idServiceBean, prepend); - } else { - return generateIdentifierFromStoredProcedureDependent(datafile, idServiceBean, prepend); - } - default: - /* Should we throw an exception instead?? -- L.A. 4.6.2 */ - return generateIdentifierAsRandomString(datafile, idServiceBean, prepend); - } - } - - private String generateIdentifierAsRandomString(DataFile datafile, GlobalIdServiceBean idServiceBean, String prepend) { - String identifier = null; - do { - identifier = prepend + RandomStringUtils.randomAlphanumeric(6).toUpperCase(); - } while (!isGlobalIdUnique(identifier, datafile, idServiceBean)); - - return identifier; - } - - - private String generateIdentifierFromStoredProcedureIndependent(DataFile datafile, GlobalIdServiceBean idServiceBean, String prepend) { - String identifier; - do { - StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("Dataset.generateIdentifierFromStoredProcedure"); - query.execute(); - String identifierFromStoredProcedure = (String) query.getOutputParameterValue(1); - // some diagnostics here maybe - is it possible to determine that it's failing - // because the stored procedure hasn't been created in the database? - if (identifierFromStoredProcedure == null) { - return null; - } - identifier = prepend + identifierFromStoredProcedure; - } while (!isGlobalIdUnique(identifier, datafile, idServiceBean)); - - return identifier; - } - - private String generateIdentifierFromStoredProcedureDependent(DataFile datafile, GlobalIdServiceBean idServiceBean, String prepend) { - String identifier; - Long retVal; - - retVal = new Long(0); - - do { - retVal++; - identifier = prepend + retVal.toString(); - - } while (!isGlobalIdUnique(identifier, datafile, idServiceBean)); - - return identifier; - } /** * Check that a identifier entered by the user is unique (not currently used @@ -1506,7 +1439,7 @@ private String generateIdentifierFromStoredProcedureDependent(DataFile datafile, * @param idServiceBean * @return {@code true} iff the global identifier is unique. */ - public boolean isGlobalIdUnique(String userIdentifier, DataFile datafile, GlobalIdServiceBean idServiceBean) { +/* public boolean isGlobalIdUnique(String userIdentifier, DataFile datafile, GlobalIdServiceBean idServiceBean) { String testProtocol = ""; String testAuthority = ""; if (datafile.getAuthority() != null){ @@ -1537,7 +1470,7 @@ public boolean isGlobalIdUnique(String userIdentifier, DataFile datafile, Global return u; } - +*/ public void finalizeFileDelete(Long dataFileId, String storageLocation) throws IOException { // Verify that the DataFile no longer exists: if (find(dataFileId) != null) { diff --git a/src/main/java/edu/harvard/iq/dataverse/Dataset.java b/src/main/java/edu/harvard/iq/dataverse/Dataset.java index d7e7271738d..683b6687c8b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/Dataset.java +++ b/src/main/java/edu/harvard/iq/dataverse/Dataset.java @@ -258,7 +258,7 @@ public void setFileAccessRequest(boolean fileAccessRequest) { } public String getPersistentURL() { - return new GlobalId(this).toURL().toString(); + return this.getGlobalId().asURL(); } public List getFiles() { @@ -765,13 +765,13 @@ public String getLocalURL() { public String getRemoteArchiveURL() { if (isHarvested()) { if (HarvestingClient.HARVEST_STYLE_DATAVERSE.equals(this.getHarvestedFrom().getHarvestStyle())) { - return this.getHarvestedFrom().getArchiveUrl() + "/dataset.xhtml?persistentId=" + getGlobalIdString(); + return this.getHarvestedFrom().getArchiveUrl() + "/dataset.xhtml?persistentId=" + getGlobalId().asString(); } else if (HarvestingClient.HARVEST_STYLE_VDC.equals(this.getHarvestedFrom().getHarvestStyle())) { String rootArchiveUrl = this.getHarvestedFrom().getHarvestingUrl(); int c = rootArchiveUrl.indexOf("/OAIHandler"); if (c > 0) { rootArchiveUrl = rootArchiveUrl.substring(0, c); - return rootArchiveUrl + "/faces/study/StudyPage.xhtml?globalId=" + getGlobalIdString(); + return rootArchiveUrl + "/faces/study/StudyPage.xhtml?globalId=" + getGlobalId().asString(); } } else if (HarvestingClient.HARVEST_STYLE_ICPSR.equals(this.getHarvestedFrom().getHarvestStyle())) { // For the ICPSR, it turns out that the best thing to do is to @@ -915,4 +915,8 @@ public DatasetThumbnail getDatasetThumbnail(DatasetVersion datasetVersion, int s return DatasetUtil.getThumbnail(this, datasetVersion, size); } + @Override + public String getTargetUrl() { + return Dataset.TARGET_URL; + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 429a0d7a4e4..e6745247071 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -2049,7 +2049,7 @@ private String init(boolean initFull) { if ( isEmpty(dataset.getIdentifier()) && systemConfig.directUploadEnabled(dataset) ) { CommandContext ctxt = commandEngine.getContext(); GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(ctxt); - dataset.setIdentifier(ctxt.datasets().generateDatasetIdentifier(dataset, idServiceBean)); + dataset.setIdentifier(idServiceBean.generateDatasetIdentifier(dataset)); } dataverseTemplates.addAll(dataverseService.find(ownerId).getTemplates()); if (!dataverseService.find(ownerId).isTemplateRoot()) { @@ -2862,9 +2862,9 @@ public String refresh() { //SEK 12/20/2019 - since we are ingesting a file we know that there is a current draft version lockedDueToIngestVar = null; if (canViewUnpublishedDataset()) { - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&showIngestSuccess=true&version=DRAFT&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&showIngestSuccess=true&version=DRAFT&faces-redirect=true"; } else { - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&showIngestSuccess=true&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&showIngestSuccess=true&faces-redirect=true"; } } @@ -3791,7 +3791,7 @@ private String returnToLatestVersion(){ setReleasedVersionTabList(resetReleasedVersionTabList()); newFiles.clear(); editMode = null; - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&version="+ workingVersion.getFriendlyVersionNumber() + "&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&version="+ workingVersion.getFriendlyVersionNumber() + "&faces-redirect=true"; } private String returnToDatasetOnly(){ @@ -3801,7 +3801,7 @@ private String returnToDatasetOnly(){ } private String returnToDraftVersion(){ - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&version=DRAFT" + "&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&version=DRAFT" + "&faces-redirect=true"; } public String cancel() { @@ -4422,7 +4422,7 @@ public List< String[]> getExporters(){ String[] temp = new String[2]; temp[0] = formatDisplayName; - temp[1] = myHostURL + "/api/datasets/export?exporter=" + formatName + "&persistentId=" + dataset.getGlobalIdString(); + temp[1] = myHostURL + "/api/datasets/export?exporter=" + formatName + "&persistentId=" + dataset.getGlobalId().asString(); retList.add(temp); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java index 91ec050fe5c..bf36fb469bd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetServiceBean.java @@ -280,12 +280,12 @@ public Dataset merge( Dataset ds ) { } public Dataset findByGlobalId(String globalId) { - Dataset retVal = (Dataset) dvObjectService.findByGlobalId(globalId, "Dataset"); + Dataset retVal = (Dataset) dvObjectService.findByGlobalId(globalId, DvObject.DType.Dataset); if (retVal != null){ return retVal; } else { //try to find with alternative PID - return (Dataset) dvObjectService.findByGlobalId(globalId, "Dataset", true); + return (Dataset) dvObjectService.findByAltGlobalId(globalId, DvObject.DType.Dataset); } } @@ -316,85 +316,11 @@ public void instantiateDatasetInNewTransaction(Long id, boolean includeVariables } } - public String generateDatasetIdentifier(Dataset dataset, GlobalIdServiceBean idServiceBean) { - String identifierType = settingsService.getValueForKey(SettingsServiceBean.Key.IdentifierGenerationStyle, "randomString"); - String shoulder = settingsService.getValueForKey(SettingsServiceBean.Key.Shoulder, ""); - - switch (identifierType) { - case "randomString": - return generateIdentifierAsRandomString(dataset, idServiceBean, shoulder); - case "storedProcGenerated": - return generateIdentifierFromStoredProcedure(dataset, idServiceBean, shoulder); - default: - /* Should we throw an exception instead?? -- L.A. 4.6.2 */ - return generateIdentifierAsRandomString(dataset, idServiceBean, shoulder); - } - } - - private String generateIdentifierAsRandomString(Dataset dataset, GlobalIdServiceBean idServiceBean, String shoulder) { - String identifier = null; - do { - identifier = shoulder + RandomStringUtils.randomAlphanumeric(6).toUpperCase(); - } while (!isIdentifierLocallyUnique(identifier, dataset)); - return identifier; - } - - private String generateIdentifierFromStoredProcedure(Dataset dataset, GlobalIdServiceBean idServiceBean, String shoulder) { - - String identifier; - do { - StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("Dataset.generateIdentifierFromStoredProcedure"); - query.execute(); - String identifierFromStoredProcedure = (String) query.getOutputParameterValue(1); - // some diagnostics here maybe - is it possible to determine that it's failing - // because the stored procedure hasn't been created in the database? - if (identifierFromStoredProcedure == null) { - return null; - } - identifier = shoulder + identifierFromStoredProcedure; - } while (!isIdentifierLocallyUnique(identifier, dataset)); - - return identifier; - } - - /** - * Check that a identifier entered by the user is unique (not currently used - * for any other study in this Dataverse Network) also check for duplicate - * in EZID if needed - * @param userIdentifier - * @param dataset - * @param persistentIdSvc - * @return {@code true} if the identifier is unique, {@code false} otherwise. - */ - public boolean isIdentifierUnique(String userIdentifier, Dataset dataset, GlobalIdServiceBean persistentIdSvc) { - if ( ! isIdentifierLocallyUnique(userIdentifier, dataset) ) return false; // duplication found in local database - - // not in local DB, look in the persistent identifier service - try { - return ! persistentIdSvc.alreadyExists(dataset); - } catch (Exception e){ - //we can live with failure - means identifier not found remotely - } - - return true; - } - - public boolean isIdentifierLocallyUnique(Dataset dataset) { - return isIdentifierLocallyUnique(dataset.getIdentifier(), dataset); - } - - public boolean isIdentifierLocallyUnique(String identifier, Dataset dataset) { - return em.createNamedQuery("Dataset.findByIdentifierAuthorityProtocol") - .setParameter("identifier", identifier) - .setParameter("authority", dataset.getAuthority()) - .setParameter("protocol", dataset.getProtocol()) - .getResultList().isEmpty(); - } public Long getMaximumExistingDatafileIdentifier(Dataset dataset) { //Cannot rely on the largest table id having the greatest identifier counter - long zeroFiles = new Long(0); + long zeroFiles = 0L; Long retVal = zeroFiles; Long testVal; List idResults; @@ -411,7 +337,7 @@ public Long getMaximumExistingDatafileIdentifier(Dataset dataset) { for (Object raw: idResults){ String identifier = (String) raw; identifier = identifier.substring(identifier.lastIndexOf("/") + 1); - testVal = new Long(identifier) ; + testVal = Long.valueOf(identifier) ; if (testVal > retVal){ retVal = testVal; } @@ -781,10 +707,10 @@ public void exportAllDatasets(boolean forceReExport) { countAll++; try { recordService.exportAllFormatsInNewTransaction(dataset); - exportLogger.info("Success exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalIdString()); + exportLogger.info("Success exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalId().asString()); countSuccess++; } catch (Exception ex) { - exportLogger.info("Error exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalIdString() + "; " + ex.getMessage()); + exportLogger.info("Error exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalId().asString() + "; " + ex.getMessage()); countError++; } } @@ -821,9 +747,9 @@ public void exportDataset(Dataset dataset, boolean forceReExport) { || dataset.getLastExportTime().before(publicationDate)))) { try { recordService.exportAllFormatsInNewTransaction(dataset); - logger.info("Success exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalIdString()); + logger.info("Success exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalId().asString()); } catch (Exception ex) { - logger.info("Error exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalIdString() + "; " + ex.getMessage()); + logger.info("Error exporting dataset: " + dataset.getDisplayName() + " " + dataset.getGlobalId().asString() + "; " + ex.getMessage()); } } } @@ -1019,7 +945,7 @@ public void obtainPersistentIdentifiersForDatafiles(Dataset dataset) { maxIdentifier++; datafile.setIdentifier(datasetIdentifier + "/" + maxIdentifier.toString()); } else { - datafile.setIdentifier(fileService.generateDataFileIdentifier(datafile, idServiceBean)); + datafile.setIdentifier(idServiceBean.generateDataFileIdentifier(datafile)); } if (datafile.getProtocol() == null) { diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java index c21861a1bf4..a043d110473 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java @@ -7,6 +7,7 @@ import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.dataset.DatasetUtil; import edu.harvard.iq.dataverse.license.License; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.util.FileUtil; import edu.harvard.iq.dataverse.util.StringUtil; import edu.harvard.iq.dataverse.util.SystemConfig; @@ -15,9 +16,7 @@ import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import edu.harvard.iq.dataverse.workflows.WorkflowComment; import java.io.Serializable; -import java.net.URL; import java.sql.Timestamp; -import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -389,7 +388,7 @@ public void setDeaccessionLink(String deaccessionLink) { } public GlobalId getDeaccessionLinkAsGlobalId() { - return new GlobalId(deaccessionLink); + return PidUtil.parseAsGlobalID(deaccessionLink); } public Date getCreateTime() { @@ -2039,10 +2038,8 @@ public String getJsonLd() { for (FileMetadata fileMetadata : fileMetadatasSorted) { JsonObjectBuilder fileObject = NullSafeJsonBuilder.jsonObjectBuilder(); String filePidUrlAsString = null; - URL filePidUrl = fileMetadata.getDataFile().getGlobalId().toURL(); - if (filePidUrl != null) { - filePidUrlAsString = filePidUrl.toString(); - } + GlobalId gid = fileMetadata.getDataFile().getGlobalId(); + filePidUrlAsString = gid != null ? gid.asURL() : null; fileObject.add("@type", "DataDownload"); fileObject.add("name", fileMetadata.getLabel()); fileObject.add("encodingFormat", fileMetadata.getDataFile().getContentType()); diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java index 23fc1961b7d..439e4b17ed4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionServiceBean.java @@ -2,6 +2,7 @@ import edu.harvard.iq.dataverse.DatasetVersion.VersionState; import edu.harvard.iq.dataverse.ingest.IngestUtil; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.search.IndexServiceBean; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; @@ -559,7 +560,7 @@ public RetrieveDatasetVersionResponse retrieveDatasetVersionByPersistentId(Strin */ GlobalId parsedId; try{ - parsedId = new GlobalId(persistentId); // [ protocol, authority, identifier] + parsedId = PidUtil.parseAsGlobalID(persistentId); // [ protocol, authority, identifier] } catch (IllegalArgumentException e){ logger.log(Level.WARNING, "Failed to parse persistentID: {0}", persistentId); return null; @@ -892,7 +893,7 @@ public void populateDatasetSearchCard(SolrSearchResult solrSearchResult) { if (searchResult.length == 5) { Dataset datasetEntity = new Dataset(); String globalIdentifier = solrSearchResult.getIdentifier(); - GlobalId globalId = new GlobalId(globalIdentifier); + GlobalId globalId = PidUtil.parseAsGlobalID(globalIdentifier); datasetEntity.setProtocol(globalId.getProtocol()); datasetEntity.setAuthority(globalId.getAuthority()); diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java index 9cc611e146a..9c47a58811a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java @@ -164,7 +164,7 @@ public String save() { try { DatasetThumbnail datasetThumbnailFromCommand = commandEngine.submit(updateDatasetThumbnailCommand); JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("dataset.thumbnailsAndWidget.success")); - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&faces-redirect=true"; } catch (CommandException ex) { String error = ex.getLocalizedMessage(); /** @@ -179,7 +179,7 @@ public String save() { public String cancel() { logger.fine("cancel clicked"); - return "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&faces-redirect=true"; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DvObject.java b/src/main/java/edu/harvard/iq/dataverse/DvObject.java index 09a2ef85893..854888737ee 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DvObject.java +++ b/src/main/java/edu/harvard/iq/dataverse/DvObject.java @@ -1,6 +1,8 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; + import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -8,6 +10,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.logging.Logger; + import javax.persistence.*; /** @@ -51,10 +55,19 @@ uniqueConstraints = {@UniqueConstraint(columnNames = {"authority,protocol,identifier"}),@UniqueConstraint(columnNames = {"owner_id,storageidentifier"})}) public abstract class DvObject extends DataverseEntity implements java.io.Serializable { - public static final String DATAVERSE_DTYPE_STRING = "Dataverse"; - public static final String DATASET_DTYPE_STRING = "Dataset"; - public static final String DATAFILE_DTYPE_STRING = "DataFile"; - public static final List DTYPE_LIST = Arrays.asList(DATAVERSE_DTYPE_STRING, DATASET_DTYPE_STRING, DATAFILE_DTYPE_STRING); + private static final Logger logger = Logger.getLogger(DvObject.class.getCanonicalName()); + + public enum DType { + Dataverse("Dataverse"), Dataset("Dataset"),DataFile("DataFile"); + + String dtype; + DType(String dt) { + dtype = dt; + } + public String getDType() { + return dtype; + } + } public static final Visitor NamePrinter = new Visitor(){ @@ -140,6 +153,8 @@ public String visit(DataFile df) { private boolean identifierRegistered; + private transient GlobalId globalId = null; + @OneToMany(mappedBy = "dvObject", cascade = CascadeType.ALL, orphanRemoval = true) private Set alternativePersistentIndentifiers; @@ -272,6 +287,8 @@ public String getProtocol() { public void setProtocol(String protocol) { this.protocol = protocol; + //Remove cached value + globalId=null; } public String getAuthority() { @@ -280,6 +297,8 @@ public String getAuthority() { public void setAuthority(String authority) { this.authority = authority; + //Remove cached value + globalId=null; } public Date getGlobalIdCreateTime() { @@ -296,6 +315,8 @@ public String getIdentifier() { public void setIdentifier(String identifier) { this.identifier = identifier; + //Remove cached value + globalId=null; } public boolean isIdentifierRegistered() { @@ -306,22 +327,13 @@ public void setIdentifierRegistered(boolean identifierRegistered) { this.identifierRegistered = identifierRegistered; } - /** - * - * @return This object's global id in a string form. - * @deprecated use {@code dvobj.getGlobalId().asString()}. - */ - public String getGlobalIdString() { - final GlobalId globalId = getGlobalId(); - return globalId != null ? globalId.asString() : null; - } - public void setGlobalId( GlobalId pid ) { if ( pid == null ) { setProtocol(null); setAuthority(null); setIdentifier(null); } else { + //These reset globalId=null setProtocol(pid.getProtocol()); setAuthority(pid.getAuthority()); setIdentifier(pid.getIdentifier()); @@ -329,10 +341,11 @@ public void setGlobalId( GlobalId pid ) { } public GlobalId getGlobalId() { - // FIXME should return NULL when the fields are null. Currenntly, - // a lot of code depends call this method, so this fix can't be - // a part of the current PR. - return new GlobalId(getProtocol(), getAuthority(), getIdentifier()); + // Cache this + if ((globalId == null) && !(getProtocol() == null || getAuthority() == null || getIdentifier() == null)) { + globalId = PidUtil.parseAsGlobalID(getProtocol(), getAuthority(), getIdentifier()); + } + return globalId; } public abstract T accept(Visitor v); @@ -420,17 +433,7 @@ public String getAuthorString(){ } public String getTargetUrl(){ - if (this instanceof Dataverse){ - throw new UnsupportedOperationException("Not supported yet."); - } - if (this instanceof Dataset){ - return Dataset.TARGET_URL; - } - if (this instanceof DataFile){ - return DataFile.TARGET_URL; - } throw new UnsupportedOperationException("Not supported yet. New DVObject Instance?"); - } public String getYearPublishedCreated(){ diff --git a/src/main/java/edu/harvard/iq/dataverse/DvObjectServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DvObjectServiceBean.java index 01b0890d588..e22e2f188fd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DvObjectServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DvObjectServiceBean.java @@ -1,6 +1,8 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; + import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; @@ -19,6 +21,8 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; + import org.apache.commons.lang3.StringUtils; import org.ocpsoft.common.util.Strings; @@ -79,46 +83,73 @@ public boolean checkExists(Long id) { Long result =(Long)query.getSingleResult(); return result > 0; } - // FIXME This type-by-string has to go, in favor of passing a class parameter. - public DvObject findByGlobalId(String globalIdString, String typeString) { - return findByGlobalId(globalIdString, typeString, false); + + public DvObject findByGlobalId(String globalIdString, DvObject.DType dtype) { + try { + GlobalId gid = PidUtil.parseAsGlobalID(globalIdString); + return findByGlobalId(gid, dtype); + } catch (IllegalArgumentException iae) { + logger.fine("Invalid identifier: " + globalIdString); + return null; + } + } - // FIXME This type-by-string has to go, in favor of passing a class parameter. - public DvObject findByGlobalId(String globalIdString, String typeString, Boolean altId) { - + public DvObject findByAltGlobalId(String globalIdString, DvObject.DType dtype) { try { - GlobalId gid = new GlobalId(globalIdString); + GlobalId gid = PidUtil.parseAsGlobalID(globalIdString); + return findByAltGlobalId(gid, dtype); + } catch (IllegalArgumentException iae) { + logger.fine("Invalid alternate identifier: " + globalIdString); + return null; + } - DvObject foundDvObject = null; - try { - Query query; - if (altId) { - query = em.createNamedQuery("DvObject.findByAlternativeGlobalId"); - } else{ - query = em.createNamedQuery("DvObject.findByGlobalId"); - } - query.setParameter("identifier", gid.getIdentifier()); - query.setParameter("protocol", gid.getProtocol()); - query.setParameter("authority", gid.getAuthority()); - query.setParameter("dtype", typeString); - foundDvObject = (DvObject) query.getSingleResult(); - } catch (javax.persistence.NoResultException e) { - // (set to .info, this can fill the log file with thousands of - // these messages during a large harvest run) - logger.fine("no dvObject found: " + globalIdString); - // DO nothing, just return null. - return null; - } catch (Exception ex) { - logger.info("Exception caught in findByGlobalId: " + ex.getLocalizedMessage()); - return null; - } - return foundDvObject; + } - } catch (IllegalArgumentException iae) { - logger.info("Invalid identifier: " + globalIdString); + public DvObject findByGlobalId(GlobalId globalId, DvObject.DType dtype) { + Query query = em.createNamedQuery("DvObject.findByGlobalId"); + return runFindByGlobalId(query, globalId, dtype); + } + + public DvObject findByAltGlobalId(GlobalId globalId, DvObject.DType dtype) { + Query query = em.createNamedQuery("DvObject.findByAlternativeGlobalId"); + return runFindByGlobalId(query, globalId, dtype); + } + + private DvObject runFindByGlobalId(Query query, GlobalId gid, DvObject.DType dtype) { + DvObject foundDvObject = null; + try { + query.setParameter("identifier", gid.getIdentifier()); + query.setParameter("protocol", gid.getProtocol()); + query.setParameter("authority", gid.getAuthority()); + query.setParameter("dtype", dtype.getDType()); + foundDvObject = (DvObject) query.getSingleResult(); + } catch (javax.persistence.NoResultException e) { + // (set to .info, this can fill the log file with thousands of + // these messages during a large harvest run) + logger.fine("no dvObject found: " + gid.asString()); + // DO nothing, just return null. + return null; + } catch (Exception ex) { + logger.info("Exception caught in findByGlobalId: " + ex.getLocalizedMessage()); return null; } + return foundDvObject; + } + + public DvObject findByGlobalId(GlobalId globalId) { + return (DvObject) em.createNamedQuery("DvObject.findByProtocolIdentifierAuthority") + .setParameter("identifier", globalId.getIdentifier()) + .setParameter("authority", globalId.getAuthority()) + .setParameter("protocol", globalId.getProtocol()).getSingleResult(); + } + + public boolean isGlobalIdLocallyUnique(GlobalId globalId) { + return em.createNamedQuery("DvObject.findByProtocolIdentifierAuthority") + .setParameter("identifier", globalId.getIdentifier()) + .setParameter("authority", globalId.getAuthority()) + .setParameter("protocol", globalId.getProtocol()) + .getResultList().isEmpty(); } public DvObject updateContentIndexTime(DvObject dvObject) { @@ -317,4 +348,11 @@ public Map getObjectPathsByIds(Set objectIds){ } return ret; } + + public String generateNewIdentifierByStoredProcedure() { + StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("Dataset.generateIdentifierFromStoredProcedure"); + query.execute(); + return (String) query.getOutputParameterValue(1); + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java b/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java index b4efe7ec41d..7185887ecc3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java +++ b/src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java @@ -18,6 +18,7 @@ import edu.harvard.iq.dataverse.engine.command.exception.PermissionException; import edu.harvard.iq.dataverse.ingest.IngestServiceBean; import edu.harvard.iq.dataverse.pidproviders.FakePidProviderServiceBean; +import edu.harvard.iq.dataverse.pidproviders.PermaLinkPidProviderServiceBean; import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean; import edu.harvard.iq.dataverse.search.IndexBatchServiceBean; import edu.harvard.iq.dataverse.search.IndexServiceBean; @@ -124,6 +125,9 @@ public class EjbDataverseEngine { @EJB HandlenetServiceBean handleNet; + @EJB + PermaLinkPidProviderServiceBean permaLinkProvider; + @EJB SettingsServiceBean settings; @@ -496,6 +500,11 @@ public HandlenetServiceBean handleNet() { return handleNet; } + @Override + public PermaLinkPidProviderServiceBean permaLinkProvider() { + return permaLinkProvider; + } + @Override public SettingsServiceBean settings() { return settings; diff --git a/src/main/java/edu/harvard/iq/dataverse/FilePage.java b/src/main/java/edu/harvard/iq/dataverse/FilePage.java index 228db0a7584..0c15aba852b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FilePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/FilePage.java @@ -364,9 +364,9 @@ public List< String[]> getExporters(){ // Not all metadata exports should be presented to the web users! // Some are only for harvesting clients. - String[] temp = new String[2]; + String[] temp = new String[2]; temp[0] = formatDisplayName; - temp[1] = myHostURL + "/api/datasets/export?exporter=" + formatName + "&persistentId=" + fileMetadata.getDatasetVersion().getDataset().getGlobalIdString(); + temp[1] = myHostURL + "/api/datasets/export?exporter=" + formatName + "&persistentId=" + fileMetadata.getDatasetVersion().getDataset().getGlobalId().asString(); retList.add(temp); } } @@ -726,7 +726,7 @@ public boolean isThumbnailAvailable(FileMetadata fileMetadata) { private String returnToDatasetOnly(){ - return "/dataset.xhtml?persistentId=" + editDataset.getGlobalIdString() + "&version=DRAFT" + "&faces-redirect=true"; + return "/dataset.xhtml?persistentId=" + editDataset.getGlobalId().asString() + "&version=DRAFT" + "&faces-redirect=true"; } private String returnToDraftVersion(){ @@ -858,9 +858,9 @@ public String getComputeUrl() throws IOException { swiftObject.open(); //generate a temp url for a file if (isHasPublicStore()) { - return settingsService.getValueForKey(SettingsServiceBean.Key.ComputeBaseUrl) + "?" + this.getFile().getOwner().getGlobalIdString() + "=" + swiftObject.getSwiftFileName(); + return settingsService.getValueForKey(SettingsServiceBean.Key.ComputeBaseUrl) + "?" + this.getFile().getOwner().getGlobalId().asString() + "=" + swiftObject.getSwiftFileName(); } - return settingsService.getValueForKey(SettingsServiceBean.Key.ComputeBaseUrl) + "?" + this.getFile().getOwner().getGlobalIdString() + "=" + swiftObject.getSwiftFileName() + "&temp_url_sig=" + swiftObject.getTempUrlSignature() + "&temp_url_expires=" + swiftObject.getTempUrlExpiry(); + return settingsService.getValueForKey(SettingsServiceBean.Key.ComputeBaseUrl) + "?" + this.getFile().getOwner().getGlobalId().asString() + "=" + swiftObject.getSwiftFileName() + "&temp_url_sig=" + swiftObject.getTempUrlSignature() + "&temp_url_expires=" + swiftObject.getTempUrlExpiry(); } return ""; } diff --git a/src/main/java/edu/harvard/iq/dataverse/GlobalId.java b/src/main/java/edu/harvard/iq/dataverse/GlobalId.java index 20b280771fc..890b146a61c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GlobalId.java +++ b/src/main/java/edu/harvard/iq/dataverse/GlobalId.java @@ -6,7 +6,7 @@ package edu.harvard.iq.dataverse; -import edu.harvard.iq.dataverse.settings.SettingsServiceBean; +import edu.harvard.iq.dataverse.pidproviders.PermaLinkPidProviderServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import static edu.harvard.iq.dataverse.util.StringUtil.isEmpty; import java.net.MalformedURLException; @@ -16,7 +16,6 @@ import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.ejb.EJB; /** * @@ -24,55 +23,28 @@ */ public class GlobalId implements java.io.Serializable { - public static final String DOI_PROTOCOL = "doi"; - public static final String HDL_PROTOCOL = "hdl"; - public static final String DOI_RESOLVER_URL = "https://doi.org/"; - public static final String DXDOI_RESOLVER_URL = "https://dx.doi.org/"; - public static final String HDL_RESOLVER_URL = "https://hdl.handle.net/"; - public static final String HTTP_DOI_RESOLVER_URL = "http://doi.org/"; - public static final String HTTP_DXDOI_RESOLVER_URL = "http://dx.doi.org/"; - public static final String HTTP_HDL_RESOLVER_URL = "http://hdl.handle.net/"; - - public static Optional parse(String identifierString) { - try { - return Optional.of(new GlobalId(identifierString)); - } catch ( IllegalArgumentException _iae) { - return Optional.empty(); - } - } - private static final Logger logger = Logger.getLogger(GlobalId.class.getName()); - - @EJB - SettingsServiceBean settingsService; - /** - * - * @param identifier The string to be parsed - * @throws IllegalArgumentException if the passed string cannot be parsed. - */ - public GlobalId(String identifier) { - // set the protocol, authority, and identifier via parsePersistentId - if ( ! parsePersistentId(identifier) ){ - throw new IllegalArgumentException("Failed to parse identifier: " + identifier); - } - } - - public GlobalId(String protocol, String authority, String identifier) { + public GlobalId(String protocol, String authority, String identifier, String separator, String urlPrefix, String providerName) { this.protocol = protocol; this.authority = authority; this.identifier = identifier; + if(separator!=null) { + this.separator = separator; + } + this.urlPrefix = urlPrefix; + this.managingProviderName = providerName; } - public GlobalId(DvObject dvObject) { - this.authority = dvObject.getAuthority(); - this.protocol = dvObject.getProtocol(); - this.identifier = dvObject.getIdentifier(); - } - + // protocol the identifier system, e.g. "doi" + // authority the namespace that the authority manages in the identifier system + // identifier the local identifier part private String protocol; private String authority; private String identifier; + private String managingProviderName; + private String separator = "/"; + private String urlPrefix; /** * Tests whether {@code this} instance has all the data required for a @@ -87,161 +59,50 @@ public String getProtocol() { return protocol; } - public void setProtocol(String protocol) { - this.protocol = protocol; - } - public String getAuthority() { return authority; } - public void setAuthority(String authority) { - this.authority = authority; - } - public String getIdentifier() { return identifier; } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } + public String getProvider() { + return managingProviderName; + } + public String toString() { return asString(); } /** - * Returns {@code this}' string representation. Differs from {@link #toString} - * which can also contain debug data, if needed. + * Concatenate the parts that make up a Global Identifier. * - * @return The string representation of this global id. + * @return the Global Identifier, e.g. "doi:10.12345/67890" */ public String asString() { if (protocol == null || authority == null || identifier == null) { return ""; } - return protocol + ":" + authority + "/" + identifier; + return protocol + ":" + authority + separator + identifier; } - public URL toURL() { + public String asURL() { URL url = null; if (identifier == null){ return null; } try { - if (protocol.equals(DOI_PROTOCOL)){ - url = new URL(DOI_RESOLVER_URL + authority + "/" + identifier); - } else if (protocol.equals(HDL_PROTOCOL)){ - url = new URL(HDL_RESOLVER_URL + authority + "/" + identifier); - } + url = new URL(urlPrefix + authority + separator + identifier); + return url.toExternalForm(); } catch (MalformedURLException ex) { logger.log(Level.SEVERE, null, ex); - } - return url; - } - - - /** - * Parse a Persistent Id and set the protocol, authority, and identifier - * - * Example 1: doi:10.5072/FK2/BYM3IW - * protocol: doi - * authority: 10.5072 - * identifier: FK2/BYM3IW - * - * Example 2: hdl:1902.1/111012 - * protocol: hdl - * authority: 1902.1 - * identifier: 111012 - * - * @param identifierString - * @param separator the string that separates the authority from the identifier. - * @param destination the global id that will contain the parsed data. - * @return {@code destination}, after its fields have been updated, or - * {@code null} if parsing failed. - */ - private boolean parsePersistentId(String identifierString) { - - if (identifierString == null) { - return false; - } - int index1 = identifierString.indexOf(':'); - if (index1 > 0) { // ':' found with one or more characters before it - int index2 = identifierString.indexOf('/', index1 + 1); - if (index2 > 0 && (index2 + 1) < identifierString.length()) { // '/' found with one or more characters - // between ':' - protocol = identifierString.substring(0, index1); // and '/' and there are characters after '/' - if (!"doi".equals(protocol) && !"hdl".equals(protocol)) { - return false; - } - //Strip any whitespace, ; and ' from authority (should finding them cause a failure instead?) - authority = formatIdentifierString(identifierString.substring(index1 + 1, index2)); - if(testforNullTerminator(authority)) return false; - if (protocol.equals(DOI_PROTOCOL)) { - if (!this.checkDOIAuthority(authority)) { - return false; - } - } - // Passed all checks - //Strip any whitespace, ; and ' from identifier (should finding them cause a failure instead?) - identifier = formatIdentifierString(identifierString.substring(index2 + 1)); - if(testforNullTerminator(identifier)) return false; - } else { - logger.log(Level.INFO, "Error parsing identifier: {0}: '':/'' not found in string", identifierString); - return false; - } - } else { - logger.log(Level.INFO, "Error parsing identifier: {0}: '':'' not found in string", identifierString); - return false; - } - return true; - } - - private static String formatIdentifierString(String str){ - - if (str == null){ - return null; - } - // remove whitespace, single quotes, and semicolons - return str.replaceAll("\\s+|'|;",""); - - /* - < (%3C) -> (%3E) -{ (%7B) -} (%7D) -^ (%5E) -[ (%5B) -] (%5D) -` (%60) -| (%7C) -\ (%5C) -+ - */ - // http://www.doi.org/doi_handbook/2_Numbering.html - } - - private static boolean testforNullTerminator(String str){ - if(str == null) { - return false; } - return str.indexOf('\u0000') > 0; - } - - private boolean checkDOIAuthority(String doiAuthority){ - - if (doiAuthority==null){ - return false; - } - - if (!(doiAuthority.startsWith("10."))){ - return false; - } - - return true; + return null; } + + /** * Verifies that the pid only contains allowed characters. * @@ -257,26 +118,5 @@ public static boolean verifyImportCharacters(String pidParam) { return m.matches(); } - /** - * Convenience method to get the internal form of a PID string when it may be in - * the https:// or http:// form ToDo -refactor class to allow creating a - * GlobalID from any form (which assures it has valid syntax) and then have methods to get - * the form you want. - * - * @param pidUrlString - a string assumed to be a valid PID in some form - * @return the internal form as a String - */ - public static String getInternalFormOfPID(String pidUrlString) { - String pidString = pidUrlString; - if(pidUrlString.startsWith(GlobalId.DOI_RESOLVER_URL)) { - pidString = pidUrlString.replace(GlobalId.DOI_RESOLVER_URL, (GlobalId.DOI_PROTOCOL + ":")); - } else if(pidUrlString.startsWith(GlobalId.HDL_RESOLVER_URL)) { - pidString = pidUrlString.replace(GlobalId.HDL_RESOLVER_URL, (GlobalId.HDL_PROTOCOL + ":")); - } else if(pidUrlString.startsWith(GlobalId.HTTP_DOI_RESOLVER_URL)) { - pidString = pidUrlString.replace(GlobalId.HTTP_DOI_RESOLVER_URL, (GlobalId.DOI_PROTOCOL + ":")); - } else if(pidUrlString.startsWith(GlobalId.HTTP_HDL_RESOLVER_URL)) { - pidString = pidUrlString.replace(GlobalId.HTTP_HDL_RESOLVER_URL, (GlobalId.HDL_PROTOCOL + ":")); - } - return pidString; - } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/GlobalIdServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/GlobalIdServiceBean.java index 0d64c1050b8..4ff3d6dc9ac 100644 --- a/src/main/java/edu/harvard/iq/dataverse/GlobalIdServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/GlobalIdServiceBean.java @@ -2,6 +2,8 @@ import static edu.harvard.iq.dataverse.GlobalIdServiceBean.logger; import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.pidproviders.PermaLinkPidProviderServiceBean; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key; import java.util.*; @@ -18,6 +20,8 @@ public interface GlobalIdServiceBean { boolean alreadyExists(GlobalId globalId) throws Exception; boolean registerWhenPublished(); + boolean canManagePID(); + boolean isConfigured(); List getProviderInformation(); @@ -25,15 +29,6 @@ public interface GlobalIdServiceBean { Map getIdentifierMetadata(DvObject dvo); - /** - * Concatenate the parts that make up a Global Identifier. - * @param protocol the identifier system, e.g. "doi" - * @param authority the namespace that the authority manages in the identifier system - * @param identifier the local identifier part - * @return the Global Identifier, e.g. "doi:10.12345/67890" - */ - String getIdentifierForLookup(String protocol, String authority, String identifier); - String modifyIdentifierTargetURL(DvObject dvo) throws Exception; void deleteIdentifier(DvObject dvo) throws Exception; @@ -42,18 +37,27 @@ public interface GlobalIdServiceBean { Map getMetadataForTargetURL(DvObject dvObject); - Map lookupMetadataFromIdentifier(String protocol, String authority, String identifier); - DvObject generateIdentifier(DvObject dvObject); String getIdentifier(DvObject dvObject); boolean publicizeIdentifier(DvObject studyIn); + String generateDatasetIdentifier(Dataset dataset); + String generateDataFileIdentifier(DataFile datafile); + boolean isGlobalIdUnique(GlobalId globalId); + + String getUrlPrefix(); + String getSeparator(); + static GlobalIdServiceBean getBean(String protocol, CommandContext ctxt) { final Function protocolHandler = BeanDispatcher.DISPATCHER.get(protocol); if ( protocolHandler != null ) { - return protocolHandler.apply(ctxt); + GlobalIdServiceBean theBean = protocolHandler.apply(ctxt); + if(theBean != null && theBean.isConfigured()) { + logger.fine("getBean returns " + theBean.getProviderInformation().get(0) + " for protocol " + protocol); + } + return theBean; } else { logger.log(Level.SEVERE, "Unknown protocol: {0}", protocol); return null; @@ -64,8 +68,113 @@ static GlobalIdServiceBean getBean(CommandContext ctxt) { return getBean(ctxt.settings().getValueForKey(Key.Protocol, ""), ctxt); } + public static Optional parse(String identifierString) { + try { + return Optional.of(PidUtil.parseAsGlobalID(identifierString)); + } catch ( IllegalArgumentException _iae) { + return Optional.empty(); + } + } + + /** + * Parse a Persistent Id and set the protocol, authority, and identifier + * + * Example 1: doi:10.5072/FK2/BYM3IW + * protocol: doi + * authority: 10.5072 + * identifier: FK2/BYM3IW + * + * Example 2: hdl:1902.1/111012 + * protocol: hdl + * authority: 1902.1 + * identifier: 111012 + * + * @param identifierString + * @param separator the string that separates the authority from the identifier. + * @param destination the global id that will contain the parsed data. + * @return {@code destination}, after its fields have been updated, or + * {@code null} if parsing failed. + */ + public GlobalId parsePersistentId(String identifierString); + public GlobalId parsePersistentId(String protocol, String authority, String identifier); + + + + public static boolean isValidGlobalId(String protocol, String authority, String identifier) { + if (protocol == null || authority == null || identifier == null) { + return false; + } + if(!authority.equals(GlobalIdServiceBean.formatIdentifierString(authority))) { + return false; + } + if (GlobalIdServiceBean.testforNullTerminator(authority)) { + return false; + } + if(!identifier.equals(GlobalIdServiceBean.formatIdentifierString(identifier))) { + return false; + } + if (GlobalIdServiceBean.testforNullTerminator(identifier)) { + return false; + } + return true; + } + + static String formatIdentifierString(String str){ + + if (str == null){ + return null; + } + // remove whitespace, single quotes, and semicolons + return str.replaceAll("\\s+|'|;",""); + + /* + < (%3C) +> (%3E) +{ (%7B) +} (%7D) +^ (%5E) +[ (%5B) +] (%5D) +` (%60) +| (%7C) +\ (%5C) ++ + */ + // http://www.doi.org/doi_handbook/2_Numbering.html + } + + static boolean testforNullTerminator(String str){ + if(str == null) { + return false; + } + return str.indexOf('\u0000') > 0; + } + + static boolean checkDOIAuthority(String doiAuthority){ + + if (doiAuthority==null){ + return false; + } + + if (!(doiAuthority.startsWith("10."))){ + return false; + } + + return true; + } } + +/* + * ToDo - replace this with a mechanism like BrandingUtilHelper that would read + * the config and create PidProviders, one per set of config values and serve + * those as needed. The help has to be a bean to autostart and to hand the + * required service beans to the PidProviders. That may boil down to just the + * dvObjectService (to check for local identifier conflicts) since it will be + * the helper that has to read settings/get systewmConfig values. + * + */ + /** * Static utility class for dispatching implementing beans, based on protocol and providers. * @author michael @@ -86,5 +195,7 @@ class BeanDispatcher { return null; } }); + + DISPATCHER.put(PermaLinkPidProviderServiceBean.PERMA_PROTOCOL, ctxt->ctxt.permaLinkProvider() ); } } \ No newline at end of file diff --git a/src/main/java/edu/harvard/iq/dataverse/HandlenetServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/HandlenetServiceBean.java index df16991b51e..cfd5ecf9dcb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/HandlenetServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/HandlenetServiceBean.java @@ -64,14 +64,17 @@ public class HandlenetServiceBean extends AbstractGlobalIdServiceBean { @EJB DataverseServiceBean dataverseService; @EJB - SettingsServiceBean settingsService; + SettingsServiceBean settingsService; private static final Logger logger = Logger.getLogger(HandlenetServiceBean.class.getCanonicalName()); - private static final String HANDLE_PROTOCOL_TAG = "hdl"; + public static final String HDL_PROTOCOL = "hdl"; int handlenetIndex = System.getProperty("dataverse.handlenet.index")!=null? Integer.parseInt(System.getProperty("dataverse.handlenet.index")) : 300; + public static final String HTTP_HDL_RESOLVER_URL = "http://hdl.handle.net/"; + public static final String HDL_RESOLVER_URL = "https://hdl.handle.net/"; public HandlenetServiceBean() { logger.log(Level.FINE,"Constructor"); + configured = true; } @Override @@ -81,7 +84,7 @@ public boolean registerWhenPublished() { public void reRegisterHandle(DvObject dvObject) { logger.log(Level.FINE,"reRegisterHandle"); - if (!HANDLE_PROTOCOL_TAG.equals(dvObject.getProtocol())) { + if (!HDL_PROTOCOL.equals(dvObject.getProtocol())) { logger.log(Level.WARNING, "reRegisterHandle called on a dvObject with the non-handle global id: {0}", dvObject.getId()); } @@ -325,11 +328,6 @@ public Map getIdentifierMetadata(DvObject dvObject) { throw new NotImplementedException(); } - @Override - public HashMap lookupMetadataFromIdentifier(String protocol, String authority, String identifier) { - throw new NotImplementedException(); - } - @Override public String modifyIdentifierTargetURL(DvObject dvObject) throws Exception { logger.log(Level.FINE,"modifyIdentifier"); @@ -412,7 +410,37 @@ public boolean publicizeIdentifier(DvObject dvObject) { } -} + @Override + public GlobalId parsePersistentId(String pidString) { + if (pidString.startsWith(HDL_RESOLVER_URL)) { + pidString = pidString.replace(HDL_RESOLVER_URL, (HDL_PROTOCOL + ":")); + } else if (pidString.startsWith(HTTP_HDL_RESOLVER_URL)) { + pidString = pidString.replace(HTTP_HDL_RESOLVER_URL, (HDL_PROTOCOL + ":")); + } + return super.parsePersistentId(pidString); + } + @Override + public GlobalId parsePersistentId(String protocol, String identifierString) { + if (!HDL_PROTOCOL.equals(protocol)) { + return null; + } + GlobalId globalId = super.parsePersistentId(protocol, identifierString); + return globalId; + } + + @Override + public GlobalId parsePersistentId(String protocol, String authority, String identifier) { + if (!HDL_PROTOCOL.equals(protocol)) { + return null; + } + return super.parsePersistentId(protocol, authority, identifier); + } + + @Override + public String getUrlPrefix() { + return HDL_RESOLVER_URL; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index 2bfd342d899..0eef5203d91 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -283,11 +283,11 @@ private String getDatasetManageFileAccessLink(DataFile datafile){ } private String getDatasetLink(Dataset dataset){ - return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString(); + return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString(); } private String getDatasetDraftLink(Dataset dataset){ - return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalIdString() + "&version=DRAFT" + "&faces-redirect=true"; + return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalId().asString() + "&version=DRAFT" + "&faces-redirect=true"; } private String getDataverseLink(Dataverse dataverse){ @@ -555,7 +555,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio case CHECKSUMFAIL: dataset = (Dataset) targetObject; String checksumFailMsg = BundleUtil.getStringFromBundle("notification.checksumfail", Arrays.asList( - dataset.getGlobalIdString() + dataset.getGlobalId().asString() )); logger.fine("checksumFailMsg: " + checksumFailMsg); return messageText += checksumFailMsg; @@ -564,7 +564,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio version = (DatasetVersion) targetObject; String fileImportMsg = BundleUtil.getStringFromBundle("notification.mail.import.filesystem", Arrays.asList( systemConfig.getDataverseSiteUrl(), - version.getDataset().getGlobalIdString(), + version.getDataset().getGlobalId().asString(), version.getDataset().getDisplayName() )); logger.fine("fileImportMsg: " + fileImportMsg); @@ -575,7 +575,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String uploadCompletedMessage = messageText + BundleUtil.getStringFromBundle("notification.mail.globus.upload.completed", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), comment )) ; @@ -586,7 +586,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String downloadCompletedMessage = messageText + BundleUtil.getStringFromBundle("notification.mail.globus.download.completed", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), comment )) ; @@ -596,7 +596,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String uploadCompletedWithErrorsMessage = messageText + BundleUtil.getStringFromBundle("notification.mail.globus.upload.completedWithErrors", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), comment )) ; @@ -607,7 +607,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String downloadCompletedWithErrorsMessage = messageText + BundleUtil.getStringFromBundle("notification.mail.globus.download.completedWithErrors", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), comment )) ; @@ -616,7 +616,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio case CHECKSUMIMPORT: version = (DatasetVersion) targetObject; String checksumImportMsg = BundleUtil.getStringFromBundle("notification.import.checksum", Arrays.asList( - version.getDataset().getGlobalIdString(), + version.getDataset().getGlobalId().asString(), version.getDataset().getDisplayName() )); logger.fine("checksumImportMsg: " + checksumImportMsg); @@ -632,7 +632,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String ingestedCompletedMessage = messageText + BundleUtil.getStringFromBundle("notification.ingest.completed", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), systemConfig.getGuidesBaseUrl(), systemConfig.getGuidesVersion(), @@ -645,7 +645,7 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio messageText = BundleUtil.getStringFromBundle("notification.email.greeting.html"); String ingestedCompletedWithErrorsMessage = messageText + BundleUtil.getStringFromBundle("notification.ingest.completedwitherrors", Arrays.asList( systemConfig.getDataverseSiteUrl(), - dataset.getGlobalIdString(), + dataset.getGlobalId().asString(), dataset.getDisplayName(), systemConfig.getGuidesBaseUrl(), systemConfig.getGuidesVersion(), diff --git a/src/main/java/edu/harvard/iq/dataverse/S3PackageImporter.java b/src/main/java/edu/harvard/iq/dataverse/S3PackageImporter.java index 054ed61f320..a099f5f3939 100644 --- a/src/main/java/edu/harvard/iq/dataverse/S3PackageImporter.java +++ b/src/main/java/edu/harvard/iq/dataverse/S3PackageImporter.java @@ -209,7 +209,7 @@ public DataFile createPackageDataFile(Dataset dataset, String folderName, long t GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(packageFile.getProtocol(), commandEngine.getContext()); if (packageFile.getIdentifier() == null || packageFile.getIdentifier().isEmpty()) { - String packageIdentifier = dataFileServiceBean.generateDataFileIdentifier(packageFile, idServiceBean); + String packageIdentifier = idServiceBean.generateDataFileIdentifier(packageFile); packageFile.setIdentifier(packageIdentifier); } diff --git a/src/main/java/edu/harvard/iq/dataverse/WidgetWrapper.java b/src/main/java/edu/harvard/iq/dataverse/WidgetWrapper.java index 6f6d0dfeee1..743d8f2d092 100644 --- a/src/main/java/edu/harvard/iq/dataverse/WidgetWrapper.java +++ b/src/main/java/edu/harvard/iq/dataverse/WidgetWrapper.java @@ -62,7 +62,7 @@ public boolean isWidgetTarget(DvObject dvo) { case "dataverse": break; // keep looping case "dataset": - if (((Dataset) dvo).getGlobalIdString().equals(widgetHome)) { + if (((Dataset) dvo).getGlobalId().asString().equals(widgetHome)) { return true; } break; default: diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 8590c45f91c..6b3e27becb7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -158,6 +158,9 @@ String getWrappedMessageWhenJson() { @EJB protected EjbDataverseEngine engineSvc; + @EJB + protected DvObjectServiceBean dvObjectSvc; + @EJB protected DatasetServiceBean datasetSvc; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index c8c1a18a3ff..4aeeff70e01 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -19,6 +19,7 @@ import edu.harvard.iq.dataverse.validation.EMailValidator; import edu.harvard.iq.dataverse.EjbDataverseEngine; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.Template; import edu.harvard.iq.dataverse.TemplateServiceBean; import edu.harvard.iq.dataverse.UserServiceBean; @@ -1472,7 +1473,7 @@ public Response isOrcidEnabled() { public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) { logger.info("Starting to reregister " + id + " Dataset Id. (from hdl to doi)" + new Date()); try { - if (settingsSvc.get(SettingsServiceBean.Key.Protocol.toString()).equals(GlobalId.HDL_PROTOCOL)) { + if (settingsSvc.get(SettingsServiceBean.Key.Protocol.toString()).equals(HandlenetServiceBean.HDL_PROTOCOL)) { logger.info("Bad Request protocol set to handle " ); return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.set.for.doi")); } @@ -1485,7 +1486,7 @@ public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathPa DataverseRequest r = createDataverseRequest(u); Dataset ds = findDatasetOrDie(id); - if (ds.getIdentifier() != null && !ds.getIdentifier().isEmpty() && ds.getProtocol().equals(GlobalId.HDL_PROTOCOL)) { + if (ds.getIdentifier() != null && !ds.getIdentifier().isEmpty() && ds.getProtocol().equals(HandlenetServiceBean.HDL_PROTOCOL)) { execCommand(new RegisterDvObjectCommand(r, ds, true)); } else { return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.hdl.dataset")); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index 926a98487d1..8ebeefec405 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -15,6 +15,7 @@ import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.GlobalIdServiceBean; import edu.harvard.iq.dataverse.GuestbookResponseServiceBean; import edu.harvard.iq.dataverse.GuestbookServiceBean; import edu.harvard.iq.dataverse.MetadataBlock; @@ -288,7 +289,7 @@ public Response createDataset(@Context ContainerRequestContext crc, String jsonB return created("/datasets/" + managedDs.getId(), Json.createObjectBuilder() .add("id", managedDs.getId()) - .add("persistentId", managedDs.getGlobalIdString()) + .add("persistentId", managedDs.getGlobalId().asString()) ); } catch (WrappedResponse ex) { @@ -331,7 +332,7 @@ public Response createDatasetFromJsonLd(@Context ContainerRequestContext crc, St return created("/datasets/" + managedDs.getId(), Json.createObjectBuilder() .add("id", managedDs.getId()) - .add("persistentId", managedDs.getGlobalIdString()) + .add("persistentId", managedDs.getGlobalId().asString()) ); } catch (WrappedResponse ex) { @@ -368,7 +369,7 @@ public Response importDataset(@Context ContainerRequestContext crc, String jsonB if (!GlobalId.verifyImportCharacters(pidParam)) { return badRequest("PID parameter contains characters that are not allowed by the Dataverse application. On import, the PID must only contain characters specified in this regex: " + BundleUtil.getStringFromBundle("pid.allowedCharacters")); } - Optional maybePid = GlobalId.parse(pidParam); + Optional maybePid = GlobalIdServiceBean.parse(pidParam); if (maybePid.isPresent()) { ds.setGlobalId(maybePid.get()); } else { @@ -399,7 +400,7 @@ public Response importDataset(@Context ContainerRequestContext crc, String jsonB Dataset managedDs = execCommand(new ImportDatasetCommand(ds, request)); JsonObjectBuilder responseBld = Json.createObjectBuilder() .add("id", managedDs.getId()) - .add("persistentId", managedDs.getGlobalIdString()); + .add("persistentId", managedDs.getGlobalId().asString()); if (shouldRelease) { PublishDatasetResult res = execCommand(new PublishDatasetCommand(managedDs, request, false, shouldRelease)); @@ -443,7 +444,7 @@ public Response importDatasetDdi(@Context ContainerRequestContext crc, String xm if (!GlobalId.verifyImportCharacters(pidParam)) { return badRequest("PID parameter contains characters that are not allowed by the Dataverse application. On import, the PID must only contain characters specified in this regex: " + BundleUtil.getStringFromBundle("pid.allowedCharacters")); } - Optional maybePid = GlobalId.parse(pidParam); + Optional maybePid = GlobalIdServiceBean.parse(pidParam); if (maybePid.isPresent()) { ds.setGlobalId(maybePid.get()); } else { @@ -465,7 +466,7 @@ public Response importDatasetDdi(@Context ContainerRequestContext crc, String xm JsonObjectBuilder responseBld = Json.createObjectBuilder() .add("id", managedDs.getId()) - .add("persistentId", managedDs.getGlobalIdString()); + .add("persistentId", managedDs.getGlobalId().toString()); if (shouldRelease) { DatasetVersion latestVersion = ds.getLatestVersion(); @@ -512,7 +513,7 @@ public Response recreateDataset(@Context ContainerRequestContext crc, String jso ds.getIdentifier().startsWith(settingsService.getValueForKey(SettingsServiceBean.Key.Shoulder)))) { throw new BadRequestException("Cannot recreate a dataset that has a PID that doesn't match the server's settings"); } - if(!datasetSvc.isIdentifierLocallyUnique(ds)) { + if(!dvObjectSvc.isGlobalIdLocallyUnique(ds.getGlobalId())) { throw new BadRequestException("Cannot recreate a dataset whose PID is already in use"); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Index.java b/src/main/java/edu/harvard/iq/dataverse/api/Index.java index c5102b7b71a..728d86addcb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Index.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Index.java @@ -326,7 +326,7 @@ public Response indexDatasetByPersistentId(@QueryParam("persistentId") String pe JsonObjectBuilder data = Json.createObjectBuilder(); data.add("message", "Reindexed dataset " + persistentId); data.add("id", dataset.getId()); - data.add("persistentId", dataset.getGlobalIdString()); + data.add("persistentId", dataset.getGlobalId().asString()); JsonArrayBuilder versions = Json.createArrayBuilder(); for (DatasetVersion version : dataset.getVersions()) { JsonObjectBuilder versionObject = Json.createObjectBuilder(); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java b/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java index 3912b9102e2..3b725468161 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java @@ -1,9 +1,12 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.DOIServiceBean; import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetServiceBean; import edu.harvard.iq.dataverse.DataverseRoleServiceBean; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.GlobalIdServiceBean; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.MailServiceBean; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.RoleAssignment; @@ -131,13 +134,13 @@ public Response acceptMessage(String body) { .getString("@id"); if (citedResource.getString("@type").equals(JsonLDTerm.schemaOrg("Dataset").getUrl())) { logger.fine("Raw PID: " + pid); - if (pid.startsWith(GlobalId.DOI_RESOLVER_URL)) { - pid = pid.replace(GlobalId.DOI_RESOLVER_URL, GlobalId.DOI_PROTOCOL + ":"); - } else if (pid.startsWith(GlobalId.HDL_RESOLVER_URL)) { - pid = pid.replace(GlobalId.HDL_RESOLVER_URL, GlobalId.HDL_PROTOCOL + ":"); + if (pid.startsWith(DOIServiceBean.DOI_RESOLVER_URL)) { + pid = pid.replace(DOIServiceBean.DOI_RESOLVER_URL, DOIServiceBean.DOI_PROTOCOL + ":"); + } else if (pid.startsWith(HandlenetServiceBean.HDL_RESOLVER_URL)) { + pid = pid.replace(HandlenetServiceBean.HDL_RESOLVER_URL, HandlenetServiceBean.HDL_PROTOCOL + ":"); } logger.fine("Protocol PID: " + pid); - Optional id = GlobalId.parse(pid); + Optional id = GlobalIdServiceBean.parse(pid); Dataset dataset = datasetSvc.findByGlobalId(pid); if (dataset != null) { JsonObject citingResource = Json.createObjectBuilder().add("@id", citingPID) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java index 8f6ec6b1c7d..b8721f360b9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/MakeDataCountApi.java @@ -138,6 +138,7 @@ public Response updateCitationsForDataset(@PathParam("id") String id) throws Mal try { Dataset dataset = findDatasetOrDie(id); String persistentId = dataset.getGlobalId().toString(); + //ToDo - if this isn't a DOI? // DataCite wants "doi=", not "doi:". String authorityPlusIdentifier = persistentId.replaceFirst("doi:", ""); // Request max page size and then loop to handle multiple pages diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Pids.java b/src/main/java/edu/harvard/iq/dataverse/api/Pids.java index 7ca011bc42d..b38d3606cc7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Pids.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Pids.java @@ -1,6 +1,7 @@ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.impl.DeletePidCommand; @@ -50,7 +51,8 @@ public Response getPid(@Context ContainerRequestContext crc, @QueryParam("persis String username = System.getProperty("doi.username"); String password = System.getProperty("doi.password"); try { - JsonObjectBuilder result = PidUtil.queryDoi(persistentId, baseUrl, username, password); + GlobalId globalId = PidUtil.parseAsGlobalID(persistentId); + JsonObjectBuilder result = PidUtil.queryDoi(globalId, baseUrl, username, password); return ok(result); } catch (NotFoundException ex) { return error(ex.getResponse().getStatusInfo().toEnum(), ex.getLocalizedMessage()); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/CollectionListManagerImpl.java b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/CollectionListManagerImpl.java index 2f3777ed6ab..c7ed00a23d0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/CollectionListManagerImpl.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/CollectionListManagerImpl.java @@ -78,8 +78,8 @@ public Feed listCollectionContents(IRI iri, AuthCredentials authCredentials, Swo if (!permissionService.isUserAllowedOn(user, new UpdateDatasetVersionCommand(dataset, dvReq), dataset)) { continue; } - String editUri = baseUrl + "/edit/study/" + dataset.getGlobalIdString(); - String editMediaUri = baseUrl + "/edit-media/study/" + dataset.getGlobalIdString(); + String editUri = baseUrl + "/edit/study/" + dataset.getGlobalId().asString(); + String editMediaUri = baseUrl + "/edit-media/study/" + dataset.getGlobalId().asString(); Entry entry = feed.addEntry(); entry.setId(editUri); entry.setTitle(datasetService.getTitleFromLatestVersion(dataset.getId())); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ContainerManagerImpl.java b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ContainerManagerImpl.java index 8fb55a8eaf6..b605f3717a8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ContainerManagerImpl.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ContainerManagerImpl.java @@ -86,7 +86,7 @@ public DepositReceipt getEntry(String uri, Map map, AuthCredenti Dataset dataset = datasetService.findByGlobalId(globalId); if (dataset != null) { if (!permissionService.isUserAllowedOn(user, new GetDraftDatasetVersionCommand(dvReq, dataset), dataset)) { - throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "User " + user.getDisplayInfo().getTitle() + " is not authorized to retrieve entry for " + dataset.getGlobalIdString()); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "User " + user.getDisplayInfo().getTitle() + " is not authorized to retrieve entry for " + dataset.getGlobalId().asString()); } Dataverse dvThatOwnsDataset = dataset.getOwner(); ReceiptGenerator receiptGenerator = new ReceiptGenerator(); @@ -228,21 +228,21 @@ public void deleteContainer(String uri, AuthCredentials authCredentials, SwordCo DatasetVersion.VersionState datasetVersionState = dataset.getLatestVersion().getVersionState(); if (dataset.isReleased()) { if (datasetVersionState.equals(DatasetVersion.VersionState.DRAFT)) { - logger.info("destroying working copy version of dataset " + dataset.getGlobalIdString()); + logger.info("destroying working copy version of dataset " + dataset.getGlobalId().asString()); try { engineSvc.submit(deleteDatasetVersionCommand); } catch (CommandException ex) { - throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Can't delete dataset version for " + dataset.getGlobalIdString() + ": " + ex); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Can't delete dataset version for " + dataset.getGlobalId().asString() + ": " + ex); } logger.info("dataset version deleted for dataset id " + dataset.getId()); } else if (datasetVersionState.equals(DatasetVersion.VersionState.RELEASED)) { throw new SwordError(UriRegistry.ERROR_METHOD_NOT_ALLOWED, "Deaccessioning a dataset is no longer supported as of Data Deposit API version in URL (" + swordConfiguration.getBaseUrlPathV1() + ") Equivalent functionality is being developed at https://github.com/IQSS/dataverse/issues/778"); } else if (datasetVersionState.equals(DatasetVersion.VersionState.DEACCESSIONED)) { - throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Lastest version of dataset " + dataset.getGlobalIdString() + " has already been deaccessioned."); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Lastest version of dataset " + dataset.getGlobalId().asString() + " has already been deaccessioned."); } else if (datasetVersionState.equals(DatasetVersion.VersionState.ARCHIVED)) { - throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Lastest version of dataset " + dataset.getGlobalIdString() + " has been archived and can not be deleted or deaccessioned."); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Lastest version of dataset " + dataset.getGlobalId().asString() + " has been archived and can not be deleted or deaccessioned."); } else { - throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Operation not valid for dataset " + dataset.getGlobalIdString() + " in state " + datasetVersionState); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Operation not valid for dataset " + dataset.getGlobalId().asString() + " in state " + datasetVersionState); } /** * @todo Reformat else below properly so you can diff --git a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/MediaResourceManagerImpl.java b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/MediaResourceManagerImpl.java index 5491024c73c..482e35df781 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/MediaResourceManagerImpl.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/MediaResourceManagerImpl.java @@ -111,7 +111,7 @@ public MediaResource getMediaResourceRepresentation(String uri, Map map, AuthCrede if (!permissionService.isUserAllowedOn(user, new GetDraftDatasetVersionCommand(dvReq, dataset), dataset)) { throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "user " + user.getDisplayInfo().getTitle() + " is not authorized to view dataset with global ID " + globalId); } - String feedUri = urlManager.getHostnamePlusBaseUrlPath(editUri) + "/edit/study/" + dataset.getGlobalIdString(); + String feedUri = urlManager.getHostnamePlusBaseUrlPath(editUri) + "/edit/study/" + dataset.getGlobalId().asString(); String author = dataset.getLatestVersion().getAuthorsStr(); String title = dataset.getLatestVersion().getTitle(); // in the statement, the element is called "updated" diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/DatasetDTO.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/DatasetDTO.java index d5be8f72fce..3fc31730ba2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/dto/DatasetDTO.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/DatasetDTO.java @@ -18,8 +18,6 @@ public class DatasetDTO implements java.io.Serializable { private String metadataLanguage; private DatasetVersionDTO datasetVersion; private List dataFiles; - public static final String DOI_PROTOCOL = "doi"; - public static final String HDL_PROTOCOL = "hdl"; public String getId() { return id; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java index c651db2dfae..57d7714ba77 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java @@ -1,6 +1,9 @@ package edu.harvard.iq.dataverse.api.imports; import com.google.gson.Gson; + +import edu.harvard.iq.dataverse.DOIServiceBean; +import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetFieldCompoundValue; import edu.harvard.iq.dataverse.DatasetFieldConstant; import edu.harvard.iq.dataverse.DatasetFieldServiceBean; @@ -9,11 +12,13 @@ import edu.harvard.iq.dataverse.ForeignMetadataFieldMapping; import edu.harvard.iq.dataverse.ForeignMetadataFormatMapping; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.MetadataBlockServiceBean; import edu.harvard.iq.dataverse.api.dto.*; import edu.harvard.iq.dataverse.api.dto.FieldDTO; import edu.harvard.iq.dataverse.api.dto.MetadataBlockDTO; import edu.harvard.iq.dataverse.license.LicenseServiceBean; +import edu.harvard.iq.dataverse.pidproviders.PermaLinkPidProviderServiceBean; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.StringUtil; import edu.harvard.iq.dataverse.util.json.JsonParseException; @@ -348,7 +353,7 @@ private String getOtherIdFromDTO(DatasetVersionDTO datasetVersionDTO) { if (!otherIds.isEmpty()) { // We prefer doi or hdl identifiers like "doi:10.7910/DVN/1HE30F" for (String otherId : otherIds) { - if (otherId.startsWith(GlobalId.DOI_PROTOCOL) || otherId.startsWith(GlobalId.HDL_PROTOCOL) || otherId.startsWith(GlobalId.DOI_RESOLVER_URL) || otherId.startsWith(GlobalId.HDL_RESOLVER_URL) || otherId.startsWith(GlobalId.HTTP_DOI_RESOLVER_URL) || otherId.startsWith(GlobalId.HTTP_HDL_RESOLVER_URL) || otherId.startsWith(GlobalId.DXDOI_RESOLVER_URL) || otherId.startsWith(GlobalId.HTTP_DXDOI_RESOLVER_URL)) { + if (otherId.startsWith(DOIServiceBean.DOI_PROTOCOL) || otherId.startsWith(HandlenetServiceBean.HDL_PROTOCOL) || otherId.startsWith(DOIServiceBean.DOI_RESOLVER_URL) || otherId.startsWith(HandlenetServiceBean.HDL_RESOLVER_URL) || otherId.startsWith(DOIServiceBean.HTTP_DOI_RESOLVER_URL) || otherId.startsWith(HandlenetServiceBean.HTTP_HDL_RESOLVER_URL) || otherId.startsWith(DOIServiceBean.DXDOI_RESOLVER_URL) || otherId.startsWith(DOIServiceBean.HTTP_DXDOI_RESOLVER_URL)) { return otherId; } } @@ -357,7 +362,7 @@ private String getOtherIdFromDTO(DatasetVersionDTO datasetVersionDTO) { try { HandleResolver hr = new HandleResolver(); hr.resolveHandle(otherId); - return GlobalId.HDL_PROTOCOL + ":" + otherId; + return HandlenetServiceBean.HDL_PROTOCOL + ":" + otherId; } catch (HandleException e) { logger.fine("Not a valid handle: " + e.toString()); } @@ -371,6 +376,8 @@ private String getOtherIdFromDTO(DatasetVersionDTO datasetVersionDTO) { * protocol/authority/identifier parts that are assigned to the datasetDTO. * The name reflects the original purpose but it is now used in ImportDDIServiceBean as well. */ + + //ToDo - sync with GlobalId.parsePersistentId(String) ? - that currently doesn't do URL forms, but could public String reassignIdentifierAsGlobalId(String identifierString, DatasetDTO datasetDTO) { int index1 = identifierString.indexOf(':'); @@ -382,24 +389,29 @@ public String reassignIdentifierAsGlobalId(String identifierString, DatasetDTO d String protocol = identifierString.substring(0, index1); - if (GlobalId.DOI_PROTOCOL.equals(protocol) || GlobalId.HDL_PROTOCOL.equals(protocol)) { - logger.fine("Processing hdl:- or doi:-style identifier : "+identifierString); + if (DOIServiceBean.DOI_PROTOCOL.equals(protocol) || HandlenetServiceBean.HDL_PROTOCOL.equals(protocol) || PermaLinkPidProviderServiceBean.PERMA_PROTOCOL.equals(protocol)) { + logger.fine("Processing hdl:- or doi:- or perma:-style identifier : "+identifierString); } else if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol)) { // We also recognize global identifiers formatted as global resolver URLs: - - if (identifierString.startsWith(GlobalId.HDL_RESOLVER_URL) || identifierString.startsWith(GlobalId.HTTP_HDL_RESOLVER_URL)) { + //ToDo - refactor index1 always has -1 here so that we can use index1+1 later + //ToDo - single map of protocol/url, are all three cases the same then? + if (identifierString.startsWith(HandlenetServiceBean.HDL_RESOLVER_URL) || identifierString.startsWith(HandlenetServiceBean.HTTP_HDL_RESOLVER_URL)) { logger.fine("Processing Handle identifier formatted as a resolver URL: "+identifierString); - protocol = GlobalId.HDL_PROTOCOL; - index1 = (identifierString.startsWith(GlobalId.HDL_RESOLVER_URL)) ? GlobalId.HDL_RESOLVER_URL.length() - 1 : GlobalId.HTTP_HDL_RESOLVER_URL.length() - 1; + protocol = HandlenetServiceBean.HDL_PROTOCOL; + index1 = (identifierString.startsWith(HandlenetServiceBean.HDL_RESOLVER_URL)) ? HandlenetServiceBean.HDL_RESOLVER_URL.length() - 1 : HandlenetServiceBean.HTTP_HDL_RESOLVER_URL.length() - 1; index2 = identifierString.indexOf("/", index1 + 1); - } else if (identifierString.startsWith(GlobalId.DOI_RESOLVER_URL) || identifierString.startsWith(GlobalId.HTTP_DOI_RESOLVER_URL) || identifierString.startsWith(GlobalId.DXDOI_RESOLVER_URL) || identifierString.startsWith(GlobalId.HTTP_DXDOI_RESOLVER_URL)) { + } else if (identifierString.startsWith(DOIServiceBean.DOI_RESOLVER_URL) || identifierString.startsWith(DOIServiceBean.HTTP_DOI_RESOLVER_URL) || identifierString.startsWith(DOIServiceBean.DXDOI_RESOLVER_URL) || identifierString.startsWith(DOIServiceBean.HTTP_DXDOI_RESOLVER_URL)) { logger.fine("Processing DOI identifier formatted as a resolver URL: "+identifierString); - protocol = GlobalId.DOI_PROTOCOL; - identifierString = identifierString.replace(GlobalId.DXDOI_RESOLVER_URL, GlobalId.DOI_RESOLVER_URL); - identifierString = identifierString.replace(GlobalId.HTTP_DXDOI_RESOLVER_URL, GlobalId.HTTP_DOI_RESOLVER_URL); - index1 = (identifierString.startsWith(GlobalId.DOI_RESOLVER_URL)) ? GlobalId.DOI_RESOLVER_URL.length() - 1 : GlobalId.HTTP_DOI_RESOLVER_URL.length() - 1; + protocol = DOIServiceBean.DOI_PROTOCOL; + identifierString = identifierString.replace(DOIServiceBean.DXDOI_RESOLVER_URL, DOIServiceBean.DOI_RESOLVER_URL); + identifierString = identifierString.replace(DOIServiceBean.HTTP_DXDOI_RESOLVER_URL, DOIServiceBean.HTTP_DOI_RESOLVER_URL); + index1 = (identifierString.startsWith(DOIServiceBean.DOI_RESOLVER_URL)) ? DOIServiceBean.DOI_RESOLVER_URL.length() - 1 : DOIServiceBean.HTTP_DOI_RESOLVER_URL.length() - 1; + index2 = identifierString.indexOf("/", index1 + 1); + } else if (identifierString.startsWith(PermaLinkPidProviderServiceBean.PERMA_RESOLVER_URL + Dataset.TARGET_URL)) { + protocol = PermaLinkPidProviderServiceBean.PERMA_PROTOCOL; + index1 = PermaLinkPidProviderServiceBean.PERMA_RESOLVER_URL.length() + + Dataset.TARGET_URL.length() - 1; index2 = identifierString.indexOf("/", index1 + 1); } else { logger.warning("HTTP Url in supplied as the identifier is neither a Handle nor DOI resolver: "+identifierString); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java index f914bd363f2..bee171950dc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java @@ -325,26 +325,26 @@ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest, Harve // A Global ID is required, in order for us to be able to harvest and import // this dataset: - if (StringUtils.isEmpty(ds.getGlobalIdString())) { + if (StringUtils.isEmpty(ds.getGlobalId().asString())) { throw new ImportException("The harvested metadata record with the OAI server identifier "+harvestIdentifier+" does not contain a global unique identifier that we could recognize, skipping."); } ds.setHarvestedFrom(harvestingClient); ds.setHarvestIdentifier(harvestIdentifier); - Dataset existingDs = datasetService.findByGlobalId(ds.getGlobalIdString()); + Dataset existingDs = datasetService.findByGlobalId(ds.getGlobalId().asString()); if (existingDs != null) { // If this dataset already exists IN ANOTHER DATAVERSE // we are just going to skip it! if (existingDs.getOwner() != null && !owner.getId().equals(existingDs.getOwner().getId())) { - throw new ImportException("The dataset with the global id "+ds.getGlobalIdString()+" already exists, in the dataverse "+existingDs.getOwner().getAlias()+", skipping."); + throw new ImportException("The dataset with the global id "+ds.getGlobalId().asString()+" already exists, in the dataverse "+existingDs.getOwner().getAlias()+", skipping."); } // And if we already have a dataset with this same id, in this same // dataverse, but it is LOCAL dataset (can happen!), we're going to // skip it also: if (!existingDs.isHarvested()) { - throw new ImportException("A LOCAL dataset with the global id "+ds.getGlobalIdString()+" already exists in this dataverse; skipping."); + throw new ImportException("A LOCAL dataset with the global id "+ds.getGlobalId().asString()+" already exists in this dataverse; skipping."); } // For harvested datasets, there should always only be one version. // We will replace the current version with the imported version. @@ -427,8 +427,8 @@ public JsonObjectBuilder doImport(DataverseRequest dataverseRequest, Dataverse o // For ImportType.NEW, if the user supplies a global identifier, and it's not a protocol // we support, it will be rejected. if (importType.equals(ImportType.NEW)) { - if (ds.getGlobalIdString() != null && !ds.getProtocol().equals(settingsService.getValueForKey(SettingsServiceBean.Key.Protocol, ""))) { - throw new ImportException("Could not register id " + ds.getGlobalIdString() + ", protocol not supported"); + if (ds.getGlobalId().asString() != null && !ds.getProtocol().equals(settingsService.getValueForKey(SettingsServiceBean.Key.Protocol, ""))) { + throw new ImportException("Could not register id " + ds.getGlobalId().asString() + ", protocol not supported"); } } @@ -497,7 +497,7 @@ public JsonObjectBuilder doImport(DataverseRequest dataverseRequest, Dataverse o } - Dataset existingDs = datasetService.findByGlobalId(ds.getGlobalIdString()); + Dataset existingDs = datasetService.findByGlobalId(ds.getGlobalId().asString()); if (existingDs != null) { if (importType.equals(ImportType.HARVEST)) { @@ -516,11 +516,11 @@ public JsonObjectBuilder doImport(DataverseRequest dataverseRequest, Dataverse o // check that the version number isn't already in the dataset for (DatasetVersion dsv : existingDs.getVersions()) { if (dsv.getVersionNumber().equals(ds.getLatestVersion().getVersionNumber())) { - throw new ImportException("VersionNumber " + ds.getLatestVersion().getVersionNumber() + " already exists in dataset " + existingDs.getGlobalIdString()); + throw new ImportException("VersionNumber " + ds.getLatestVersion().getVersionNumber() + " already exists in dataset " + existingDs.getGlobalId().asString()); } } DatasetVersion dsv = engineSvc.submit(new CreateDatasetVersionCommand(dataverseRequest, existingDs, ds.getVersions().get(0))); - status = " created datasetVersion, for dataset "+ dsv.getDataset().getGlobalIdString(); + status = " created datasetVersion, for dataset "+ dsv.getDataset().getGlobalId().asString(); createdId = dsv.getId(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/batch/jobs/importer/filesystem/FileRecordWriter.java b/src/main/java/edu/harvard/iq/dataverse/batch/jobs/importer/filesystem/FileRecordWriter.java index c82a5bb01eb..291396b4d33 100644 --- a/src/main/java/edu/harvard/iq/dataverse/batch/jobs/importer/filesystem/FileRecordWriter.java +++ b/src/main/java/edu/harvard/iq/dataverse/batch/jobs/importer/filesystem/FileRecordWriter.java @@ -363,7 +363,7 @@ private DataFile createPackageDataFile(List files) { GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(packageFile.getProtocol(), commandEngine.getContext()); if (packageFile.getIdentifier() == null || packageFile.getIdentifier().isEmpty()) { - packageFile.setIdentifier(dataFileServiceBean.generateDataFileIdentifier(packageFile, idServiceBean)); + packageFile.setIdentifier(idServiceBean.generateDataFileIdentifier(packageFile)); } String nonNullDefaultIfKeyNotFound = ""; String protocol = commandEngine.getContext().settings().getValueForKey(SettingsServiceBean.Key.Protocol, nonNullDefaultIfKeyNotFound); diff --git a/src/main/java/edu/harvard/iq/dataverse/datasetutility/FileVersionInfo.java b/src/main/java/edu/harvard/iq/dataverse/datasetutility/FileVersionInfo.java index c967de249ac..ca8775593b8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/datasetutility/FileVersionInfo.java +++ b/src/main/java/edu/harvard/iq/dataverse/datasetutility/FileVersionInfo.java @@ -16,6 +16,7 @@ * * @author rmp553 */ +//ToDo - used at all? public class FileVersionInfo { private Long id; diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/CommandContext.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/CommandContext.java index 8e555d5f7a2..ef5b9dba407 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/CommandContext.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/CommandContext.java @@ -33,6 +33,7 @@ import edu.harvard.iq.dataverse.engine.DataverseEngine; import edu.harvard.iq.dataverse.ingest.IngestServiceBean; import edu.harvard.iq.dataverse.pidproviders.FakePidProviderServiceBean; +import edu.harvard.iq.dataverse.pidproviders.PermaLinkPidProviderServiceBean; import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean; import edu.harvard.iq.dataverse.search.IndexBatchServiceBean; import edu.harvard.iq.dataverse.search.SolrIndexServiceBean; @@ -40,7 +41,6 @@ import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.SystemConfig; import edu.harvard.iq.dataverse.workflow.WorkflowServiceBean; -import java.util.List; import java.util.Stack; import javax.persistence.EntityManager; @@ -107,6 +107,8 @@ public interface CommandContext { public HandlenetServiceBean handleNet(); + public PermaLinkPidProviderServiceBean permaLinkProvider(); + public GuestbookServiceBean guestbooks(); public GuestbookResponseServiceBean responses(); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractCreateDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractCreateDatasetCommand.java index 1465cbd74e2..8f477a66424 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractCreateDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractCreateDatasetCommand.java @@ -75,7 +75,7 @@ public Dataset execute(CommandContext ctxt) throws CommandException { Dataset theDataset = getDataset(); GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(ctxt); if ( isEmpty(theDataset.getIdentifier()) ) { - theDataset.setIdentifier(ctxt.datasets().generateDatasetIdentifier(theDataset, idServiceBean)); + theDataset.setIdentifier(idServiceBean.generateDatasetIdentifier(theDataset)); } DatasetVersion dsv = getVersionToPersist(theDataset); @@ -105,7 +105,7 @@ public Dataset execute(CommandContext ctxt) throws CommandException { theDataset.setStorageIdentifier(driverId + DataAccess.SEPARATOR + theDataset.getAuthorityForFileStorage() + "/" + theDataset.getIdentifierForFileStorage()); } if (theDataset.getIdentifier()==null) { - theDataset.setIdentifier(ctxt.datasets().generateDatasetIdentifier(theDataset, idServiceBean)); + theDataset.setIdentifier(idServiceBean.generateDatasetIdentifier(theDataset)); } // Attempt the registration if importing dataset through the API, or the app (but not harvest) diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java index f3b75d23c63..4e3c2835382 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractDatasetCommand.java @@ -158,7 +158,7 @@ protected void registerExternalIdentifier(Dataset theDataset, CommandContext ctx int attempts = 0; if(retry) { do { - theDataset.setIdentifier(ctxt.datasets().generateDatasetIdentifier(theDataset, globalIdServiceBean)); + theDataset.setIdentifier(globalIdServiceBean.generateDatasetIdentifier(theDataset)); logger.log(Level.INFO, "Attempting to register external identifier for dataset {0} (trying: {1}).", new Object[]{theDataset.getId(), theDataset.getIdentifier()}); attempts++; diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDatasetCommand.java index 1efaf14c755..7122a4e7b7f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDatasetCommand.java @@ -71,7 +71,7 @@ public CreateNewDatasetCommand(Dataset theDataset, DataverseRequest aRequest, bo protected void additionalParameterTests(CommandContext ctxt) throws CommandException { if ( nonEmpty(getDataset().getIdentifier()) ) { GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(getDataset().getProtocol(), ctxt); - if ( !ctxt.datasets().isIdentifierUnique(getDataset().getIdentifier(), getDataset(), idServiceBean) ) { + if ( !idServiceBean.isGlobalIdUnique(getDataset().getGlobalId()) ) { throw new IllegalCommandException(String.format("Dataset with identifier '%s', protocol '%s' and authority '%s' already exists", getDataset().getIdentifier(), getDataset().getProtocol(), getDataset().getAuthority()), this); @@ -87,6 +87,9 @@ protected DatasetVersion getVersionToPersist( Dataset theDataset ) { @Override protected void handlePid(Dataset theDataset, CommandContext ctxt) throws CommandException { GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(ctxt); + if(!idServiceBean.isConfigured()) { + throw new IllegalCommandException("PID Provider " + idServiceBean.getProviderInformation().get(0) + " is not configured.", this); + } if ( !idServiceBean.registerWhenPublished() ) { // pre-register a persistent id registerExternalIdentifier(theDataset, ctxt, true); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetPublicationCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetPublicationCommand.java index 12bb3fb6a0a..cb46b36eb53 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetPublicationCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/FinalizeDatasetPublicationCommand.java @@ -368,7 +368,7 @@ private void publicizeExternalIdentifier(Dataset dataset, CommandContext ctxt) t GlobalIdServiceBean idServiceBean = GlobalIdServiceBean.getBean(protocol, ctxt); if (idServiceBean != null) { - List args = idServiceBean.getProviderInformation(); + try { String currentGlobalIdProtocol = ctxt.settings().getValueForKey(SettingsServiceBean.Key.Protocol, ""); String currentGlobalAuthority = ctxt.settings().getValueForKey(SettingsServiceBean.Key.Authority, ""); @@ -406,12 +406,12 @@ private void publicizeExternalIdentifier(Dataset dataset, CommandContext ctxt) t dataset.setIdentifierRegistered(true); } catch (Throwable e) { logger.warning("Failed to register the identifier "+dataset.getGlobalId().asString()+", or to register a file in the dataset; notifying the user(s), unlocking the dataset"); - + // Send failure notification to the user: notifyUsersDatasetPublishStatus(ctxt, dataset, UserNotification.Type.PUBLISHFAILED_PIDREG); ctxt.datasets().removeDatasetLocks(dataset, DatasetLock.Reason.finalizePublication); - throw new CommandException(BundleUtil.getStringFromBundle("dataset.publish.error", args), this); + throw new CommandException(BundleUtil.getStringFromBundle("dataset.publish.error", idServiceBean.getProviderInformation()), this); } } } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ImportDatasetCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ImportDatasetCommand.java index 807472cda08..36d5eaf6f31 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ImportDatasetCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/ImportDatasetCommand.java @@ -58,8 +58,8 @@ protected void additionalParameterTests(CommandContext ctxt) throws CommandExcep throw new IllegalCommandException("Imported datasets must have a persistent global identifier.", this); } - if ( ! ctxt.datasets().isIdentifierLocallyUnique(ds) ) { - throw new IllegalCommandException("Persistent identifier " + ds.getGlobalIdString() + " already exists in this Dataverse installation.", this); + if ( ! ctxt.dvObjects().isGlobalIdLocallyUnique(ds.getGlobalId()) ) { + throw new IllegalCommandException("Persistent identifier " + ds.getGlobalId().asString() + " already exists in this Dataverse installation.", this); } String pid = ds.getPersistentURL(); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/RegisterDvObjectCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/RegisterDvObjectCommand.java index 9169d6b4fe9..299d1a925f4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/RegisterDvObjectCommand.java +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/RegisterDvObjectCommand.java @@ -14,6 +14,7 @@ import java.sql.Timestamp; import java.util.Date; import edu.harvard.iq.dataverse.GlobalIdServiceBean; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.batch.util.LoggingUtil; import java.io.IOException; import org.apache.solr.client.solrj.SolrServerException; @@ -57,10 +58,10 @@ protected void executeImpl(CommandContext ctxt) throws CommandException { //if so, leave. if (target.getIdentifier() == null || target.getIdentifier().isEmpty()) { if (target.isInstanceofDataset()) { - target.setIdentifier(ctxt.datasets().generateDatasetIdentifier((Dataset) target, idServiceBean)); + target.setIdentifier(idServiceBean.generateDatasetIdentifier((Dataset) target)); } else { - target.setIdentifier(ctxt.files().generateDataFileIdentifier((DataFile) target, idServiceBean)); + target.setIdentifier(idServiceBean.generateDataFileIdentifier((DataFile) target)); } if (target.getProtocol() == null) { target.setProtocol(protocol); @@ -94,7 +95,7 @@ protected void executeImpl(CommandContext ctxt) throws CommandException { Dataset dataset = (Dataset) target; for (DataFile df : dataset.getFiles()) { if (df.getIdentifier() == null || df.getIdentifier().isEmpty()) { - df.setIdentifier(ctxt.files().generateDataFileIdentifier(df, idServiceBean)); + df.setIdentifier(idServiceBean.generateDataFileIdentifier(df)); if (df.getProtocol() == null || df.getProtocol().isEmpty()) { df.setProtocol(protocol); } @@ -151,7 +152,7 @@ protected void executeImpl(CommandContext ctxt) throws CommandException { private Boolean processMigrateHandle (CommandContext ctxt){ boolean retval = true; if(!target.isInstanceofDataset()) return false; - if(!target.getProtocol().equals(GlobalId.HDL_PROTOCOL)) return false; + if(!target.getProtocol().equals(HandlenetServiceBean.HDL_PROTOCOL)) return false; AlternativePersistentIdentifier api = new AlternativePersistentIdentifier(); api.setProtocol(target.getProtocol()); diff --git a/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java index eb7632dd03c..c78bb02d5c5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java @@ -30,6 +30,7 @@ import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_TYPE_TAG; import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_TYPE_UNF; import edu.harvard.iq.dataverse.export.DDIExporter; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; @@ -181,7 +182,7 @@ private static void createStdyDscr(XMLStreamWriter xmlw, DatasetDTO datasetDto) String pidUri = pid; //Some tests don't send real PIDs - don't try to get their URL form if(!pidUri.equals("null:null/null")) { - pidUri= new GlobalId(persistentProtocol + ":" + persistentAuthority + "/" + persistentId).toURL().toString(); + pidUri= PidUtil.parseAsGlobalID(persistentProtocol, persistentAuthority, persistentId).asURL(); } // The "persistentAgency" tag is used for the "agency" attribute of the // ddi section; back in the DVN3 days we used "handle" and "DOI" @@ -1347,8 +1348,8 @@ private static void createOtherMatsFromFileMetadatas(XMLStreamWriter xmlw, List< writeAttribute(xmlw, "ID", "f" + fileMetadata.getDataFile().getId()); String dfIdentifier = fileMetadata.getDataFile().getIdentifier(); if (dfIdentifier != null && !dfIdentifier.isEmpty()){ - GlobalId globalId = new GlobalId(fileMetadata.getDataFile()); - writeAttribute(xmlw, "URI", globalId.toURL().toString()); + GlobalId globalId = fileMetadata.getDataFile().getGlobalId(); + writeAttribute(xmlw, "URI", globalId.asURL()); } else { writeAttribute(xmlw, "URI", dataverseUrl + "/api/access/datafile/" + fileMetadata.getDataFile().getId()); } diff --git a/src/main/java/edu/harvard/iq/dataverse/export/dublincore/DublinCoreExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/dublincore/DublinCoreExportUtil.java index 4409d2340b1..238cea78fb5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/dublincore/DublinCoreExportUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/export/dublincore/DublinCoreExportUtil.java @@ -15,6 +15,7 @@ import edu.harvard.iq.dataverse.api.dto.MetadataBlockDTO; import edu.harvard.iq.dataverse.export.ddi.DdiExportUtil; import edu.harvard.iq.dataverse.license.License; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.util.json.JsonUtil; import java.io.ByteArrayOutputStream; import java.io.OutputStream; @@ -102,12 +103,12 @@ private static void createDC(XMLStreamWriter xmlw, DatasetDTO datasetDto, String String persistentAgency = datasetDto.getProtocol(); String persistentAuthority = datasetDto.getAuthority(); String persistentId = datasetDto.getIdentifier(); - GlobalId globalId = new GlobalId(persistentAgency, persistentAuthority, persistentId); + GlobalId globalId = PidUtil.parseAsGlobalID(persistentAgency, persistentAuthority, persistentId); writeFullElement(xmlw, dcFlavor+":"+"title", dto2Primitive(version, DatasetFieldConstant.title)); xmlw.writeStartElement(dcFlavor+":"+"identifier"); - xmlw.writeCharacters(globalId.toURL().toString()); + xmlw.writeCharacters(globalId.asURL()); xmlw.writeEndElement(); // decterms:identifier writeAuthorsElement(xmlw, version, dcFlavor); @@ -160,12 +161,12 @@ private static void createOAIDC(XMLStreamWriter xmlw, DatasetDTO datasetDto, Str String persistentAgency = datasetDto.getProtocol(); String persistentAuthority = datasetDto.getAuthority(); String persistentId = datasetDto.getIdentifier(); - GlobalId globalId = new GlobalId(persistentAgency, persistentAuthority, persistentId); + GlobalId globalId = PidUtil.parseAsGlobalID(persistentAgency, persistentAuthority, persistentId); writeFullElement(xmlw, dcFlavor+":"+"title", dto2Primitive(version, DatasetFieldConstant.title)); xmlw.writeStartElement(dcFlavor+":"+"identifier"); - xmlw.writeCharacters(globalId.toURL().toString()); + xmlw.writeCharacters(globalId.asURL()); xmlw.writeEndElement(); // decterms:identifier writeAuthorsElement(xmlw, version, dcFlavor); //creator diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java index bea3858a60e..e8dfed96281 100644 --- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java @@ -17,13 +17,16 @@ import com.google.gson.Gson; +import edu.harvard.iq.dataverse.DOIServiceBean; import edu.harvard.iq.dataverse.DatasetFieldConstant; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.TermsOfUseAndAccess; import edu.harvard.iq.dataverse.api.dto.DatasetDTO; import edu.harvard.iq.dataverse.api.dto.DatasetVersionDTO; import edu.harvard.iq.dataverse.api.dto.FieldDTO; import edu.harvard.iq.dataverse.api.dto.MetadataBlockDTO; +import edu.harvard.iq.dataverse.pidproviders.PidUtil; import edu.harvard.iq.dataverse.util.json.JsonUtil; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -70,7 +73,7 @@ private static void createOpenAire(XMLStreamWriter xmlw, DatasetDTO datasetDto) String persistentAgency = datasetDto.getProtocol(); String persistentAuthority = datasetDto.getAuthority(); String persistentId = datasetDto.getIdentifier(); - GlobalId globalId = new GlobalId(persistentAgency, persistentAuthority, persistentId); + GlobalId globalId = PidUtil.parseAsGlobalID(persistentAgency, persistentAuthority, persistentId); // The sequence is revied using sample: // https://schema.datacite.org/meta/kernel-4.0/example/datacite-example-full-v4.0.xml @@ -82,7 +85,7 @@ private static void createOpenAire(XMLStreamWriter xmlw, DatasetDTO datasetDto) String language = null; // 1, Identifier (with mandatory type sub-property) (M) - writeIdentifierElement(xmlw, globalId.toURL().toString(), language); + writeIdentifierElement(xmlw, globalId.asURL(), language); // 2, Creator (with optional given name, family name, // name identifier and affiliation sub-properties) (M) @@ -190,10 +193,10 @@ public static void writeIdentifierElement(XMLStreamWriter xmlw, String identifie if (StringUtils.isNotBlank(identifier)) { Map identifier_map = new HashMap(); - if (StringUtils.containsIgnoreCase(identifier, GlobalId.DOI_RESOLVER_URL)) { + if (StringUtils.containsIgnoreCase(identifier, DOIServiceBean.DOI_RESOLVER_URL)) { identifier_map.put("identifierType", "DOI"); identifier = StringUtils.substring(identifier, identifier.indexOf("10.")); - } else if (StringUtils.containsIgnoreCase(identifier, GlobalId.HDL_RESOLVER_URL)) { + } else if (StringUtils.containsIgnoreCase(identifier, HandlenetServiceBean.HDL_RESOLVER_URL)) { identifier_map.put("identifierType", "Handle"); if (StringUtils.contains(identifier, "http")) { identifier = identifier.replace(identifier.substring(0, identifier.indexOf("/") + 2), ""); diff --git a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java index 8b23d68f4b7..0e17d14f455 100644 --- a/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/feedback/FeedbackUtil.java @@ -56,8 +56,8 @@ public static List gatherFeedback(DvObject recipient, DataverseSession } else if (recipient.isInstanceofDataset()) { Dataset dataset = (Dataset) recipient; String datasetTitle = dataset.getLatestVersion().getTitle(); - String datasetPid = dataset.getGlobalIdString(); - String datasetContextEnding = BundleUtil.getStringFromBundle("contact.context.dataset.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, dataset.getGlobalIdString(), supportTeamName, systemEmail)); + String datasetPid = dataset.getGlobalId().asString(); + String datasetContextEnding = BundleUtil.getStringFromBundle("contact.context.dataset.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, dataset.getGlobalId().asString(), supportTeamName, systemEmail)); List datasetContacts = getDatasetContacts(dataset); for (DvObjectContact datasetContact : datasetContacts) { String contactFullName = getGreeting(datasetContact); @@ -76,7 +76,7 @@ public static List gatherFeedback(DvObject recipient, DataverseSession } else { DataFile datafile = (DataFile) recipient; String datasetTitle = datafile.getOwner().getLatestVersion().getTitle(); - String datasetPid = datafile.getOwner().getGlobalIdString(); + String datasetPid = datafile.getOwner().getGlobalId().asString(); String filename = datafile.getFileMetadatas().get(0).getLabel(); List datasetContacts = getDatasetContacts(datafile.getOwner()); String fileContextEnding = BundleUtil.getStringFromBundle("contact.context.file.ending", Arrays.asList(supportTeamName, systemEmail, dataverseSiteUrl, datafile.getId().toString(), supportTeamName, systemEmail)); diff --git a/src/main/java/edu/harvard/iq/dataverse/harvest/server/OAIRecordServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/harvest/server/OAIRecordServiceBean.java index 5a8f2f41d31..f8522c437a0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/harvest/server/OAIRecordServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/harvest/server/OAIRecordServiceBean.java @@ -113,7 +113,7 @@ public void updateOaiRecords(String setName, List datasetIds, Date updateT && (dataset.getLastExportTime() == null || dataset.getLastExportTime().before(publicationDate))) { - setUpdateLogger.fine("Attempting to run export on dataset " + dataset.getGlobalIdString()); + setUpdateLogger.fine("Attempting to run export on dataset " + dataset.getGlobalId().asString()); exportAllFormats(dataset); } @@ -147,14 +147,14 @@ public void updateOaiRecordForDataset(Dataset dataset, String setName, Map oaiRecords = findOaiRecordsByGlobalId(dataset.getGlobalIdString()); + List oaiRecords = findOaiRecordsByGlobalId(dataset.getGlobalId().asString()); if (oaiRecords != null) { DatasetVersion releasedVersion = dataset.getReleasedVersion(); @@ -195,7 +195,7 @@ public void updateOaiRecordsForDataset(Dataset dataset) { for (OAIRecord record : oaiRecords) { if (record.isRemoved()) { - logger.fine("\"un-deleting\" an existing OAI Record for " + dataset.getGlobalIdString()); + logger.fine("\"un-deleting\" an existing OAI Record for " + dataset.getGlobalId().asString()); record.setRemoved(false); record.setLastUpdateTime(new Date()); } else if (dataset.getLastExportTime().after(record.getLastUpdateTime())) { diff --git a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestUtil.java b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestUtil.java index 9484a412913..368824680c0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestUtil.java @@ -350,7 +350,7 @@ public static boolean shouldHaveUnf(DatasetVersion version) { return false; } List values = getUnfValuesOfFiles(version); - logger.fine("UNF values for files from Dataset version " + version.getSemanticVersion() + " from " + version.getDataset().getGlobalIdString() + ": " + values); + logger.fine("UNF values for files from Dataset version " + version.getSemanticVersion() + " from " + version.getDataset().getGlobalId().asString() + ": " + values); if (values.size() > 0) { return true; } else { diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 15415667f87..b1cfeb4dc84 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -6,6 +6,7 @@ import edu.harvard.iq.dataverse.DataverseRoleServiceBean; import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.DataverseSession; +import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.api.auth.AuthRequired; @@ -84,7 +85,6 @@ public class DataRetrieverAPI extends AbstractApiBean { private List roleList; private DataverseRolePermissionHelper rolePermissionHelper; - private List defaultDvObjectTypes = MyDataFilterParams.defaultDvObjectTypes; private MyDataFinder myDataFinder; private SolrQueryResponse solrQueryResponse; private AuthenticatedUser authUser = null; @@ -101,11 +101,6 @@ public DataRetrieverAPI(){ } - private int randInt(int min, int max) { - Random rand = new Random(); - return rand.nextInt((max - min) + 1) + min; - } - public String getRetrieveDataFullAPIPath(){ return DataRetrieverAPI.retrieveDataFullAPIPath; } @@ -265,7 +260,7 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ @Produces({"application/json"}) public String retrieveMyDataAsJsonString( @Context ContainerRequestContext crc, - @QueryParam("dvobject_types") List dvobject_types, + @QueryParam("dvobject_types") List dvobject_types, @QueryParam("published_states") List published_states, @QueryParam("selected_page") Integer selectedPage, @QueryParam("mydata_search_term") String searchTerm, @@ -304,7 +299,7 @@ public String retrieveMyDataAsJsonString( rolePermissionHelper = new DataverseRolePermissionHelper(roleList); - List dtypes; + List dtypes; if (dvobject_types != null){ dtypes = dvobject_types; }else{ diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFilterParams.java b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFilterParams.java index 0e99220005c..f3d09053bb1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFilterParams.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFilterParams.java @@ -33,8 +33,8 @@ public class MyDataFilterParams { // ----------------------------------- // Static Reference objects // ----------------------------------- - public static final List defaultDvObjectTypes = Arrays.asList(DvObject.DATAVERSE_DTYPE_STRING, DvObject.DATASET_DTYPE_STRING); - public static final List allDvObjectTypes = Arrays.asList(DvObject.DATAVERSE_DTYPE_STRING, DvObject.DATASET_DTYPE_STRING, DvObject.DATAFILE_DTYPE_STRING); + public static final List defaultDvObjectTypes = Arrays.asList(DvObject.DType.Dataverse, DvObject.DType.Dataset); + public static final List allDvObjectTypes = Arrays.asList(DvObject.DType.Dataverse, DvObject.DType.Dataset, DvObject.DType.Dataverse, DvObject.DType.DataFile); public static final List defaultPublishedStates = Arrays.asList(IndexServiceBean.getPUBLISHED_STRING(), IndexServiceBean.getUNPUBLISHED_STRING(), @@ -48,22 +48,23 @@ public class MyDataFilterParams { IndexServiceBean.getIN_REVIEW_STRING(), IndexServiceBean.getDEACCESSIONED_STRING());*/ - public static final HashMap sqlToSolrSearchMap ; + public static final HashMap sqlToSolrSearchMap ; static { sqlToSolrSearchMap = new HashMap<>(); - sqlToSolrSearchMap.put(DvObject.DATAVERSE_DTYPE_STRING, SearchConstants.DATAVERSES); - sqlToSolrSearchMap.put(DvObject.DATASET_DTYPE_STRING, SearchConstants.DATASETS); - sqlToSolrSearchMap.put(DvObject.DATAFILE_DTYPE_STRING, SearchConstants.FILES); + sqlToSolrSearchMap.put(DvObject.DType.Dataverse, SearchConstants.DATAVERSES); + sqlToSolrSearchMap.put(DvObject.DType.Dataset, SearchConstants.DATASETS); + sqlToSolrSearchMap.put(DvObject.DType.DataFile, SearchConstants.FILES); } - public static final HashMap userInterfaceToSqlSearchMap ; + public static final HashMap userInterfaceToSqlSearchMap ; static { userInterfaceToSqlSearchMap = new HashMap<>(); - userInterfaceToSqlSearchMap.put(DvObject.DATAVERSE_DTYPE_STRING, SearchConstants.UI_DATAVERSES); - userInterfaceToSqlSearchMap.put(DvObject.DATASET_DTYPE_STRING, SearchConstants.UI_DATAVERSES); - userInterfaceToSqlSearchMap.put(DvObject.DATAFILE_DTYPE_STRING, SearchConstants.UI_FILES); + + userInterfaceToSqlSearchMap.put(DvObject.DType.Dataverse, SearchConstants.UI_DATAVERSES); + userInterfaceToSqlSearchMap.put(DvObject.DType.Dataset, SearchConstants.UI_DATAVERSES); + userInterfaceToSqlSearchMap.put(DvObject.DType.DataFile, SearchConstants.UI_FILES); } @@ -73,7 +74,7 @@ public class MyDataFilterParams { private DataverseRequest dataverseRequest; private AuthenticatedUser authenticatedUser; private String userIdentifier; - private List dvObjectTypes; + private List dvObjectTypes; private List publicationStatuses; private List roleIds; @@ -119,7 +120,7 @@ public MyDataFilterParams(DataverseRequest dataverseRequest, DataverseRolePermis * @param publicationStatuses * @param searchTerm */ - public MyDataFilterParams(DataverseRequest dataverseRequest, List dvObjectTypes, List publicationStatuses, List roleIds, String searchTerm){ + public MyDataFilterParams(DataverseRequest dataverseRequest, List dvObjectTypes, List publicationStatuses, List roleIds, String searchTerm){ if (dataverseRequest==null){ throw new NullPointerException("MyDataFilterParams constructor: dataverseRequest cannot be null "); } @@ -194,16 +195,9 @@ private void checkParams(){ this.addError("No results. Please select one of " + StringUtils.join(MyDataFilterParams.defaultPublishedStates, ", ") + "."); return; } - - for (String dtype : this.dvObjectTypes){ - if (!DvObject.DTYPE_LIST.contains(dtype)){ - this.addError("Sorry! The type '" + dtype + "' is not known."); - return; - } - } } - public List getDvObjectTypes(){ + public List getDvObjectTypes(){ return this.dvObjectTypes; } @@ -235,19 +229,19 @@ public void addError(String s){ // start: Convenience methods for dvObjectTypes // -------------------------------------------- public boolean areDataversesIncluded(){ - if (this.dvObjectTypes.contains(DvObject.DATAVERSE_DTYPE_STRING)){ + if (this.dvObjectTypes.contains(DvObject.DType.Dataverse)){ return true; } return false; } public boolean areDatasetsIncluded(){ - if (this.dvObjectTypes.contains(DvObject.DATASET_DTYPE_STRING)){ + if (this.dvObjectTypes.contains(DvObject.DType.Dataset)){ return true; } return false; } public boolean areFilesIncluded(){ - if (this.dvObjectTypes.contains(DvObject.DATAFILE_DTYPE_STRING)){ + if (this.dvObjectTypes.contains(DvObject.DType.DataFile)){ return true; } return false; @@ -259,7 +253,7 @@ public String getSolrFragmentForDvObjectType(){ } List solrTypes = new ArrayList<>(); - for (String dtype : this.dvObjectTypes){ + for (DvObject.DType dtype : this.dvObjectTypes){ solrTypes.add(MyDataFilterParams.sqlToSolrSearchMap.get(dtype)); } @@ -318,13 +312,13 @@ public JsonObjectBuilder getDvObjectTypesAsJSON(){ JsonArrayBuilder jsonArray = Json.createArrayBuilder(); - jsonArray.add(Json.createObjectBuilder().add("value", DvObject.DATAVERSE_DTYPE_STRING) + jsonArray.add(Json.createObjectBuilder().add("value", DvObject.DType.Dataverse.getDType()) .add("label", SearchConstants.UI_DATAVERSES) .add("selected", this.areDataversesIncluded())) - .add(Json.createObjectBuilder().add("value", DvObject.DATASET_DTYPE_STRING) + .add(Json.createObjectBuilder().add("value", DvObject.DType.Dataset.getDType()) .add("label", SearchConstants.UI_DATASETS) .add("selected", this.areDatasetsIncluded())) - .add(Json.createObjectBuilder().add("value", DvObject.DATAFILE_DTYPE_STRING) + .add(Json.createObjectBuilder().add("value", DvObject.DType.DataFile.getDType()) .add("label", SearchConstants.UI_FILES) .add("selected", this.areFilesIncluded()) ); diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java index 7aa0cab13d2..2033bf80905 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataFinder.java @@ -516,8 +516,8 @@ private boolean runStep2DirectAssignments(){ this.childToParentIds.put(dvId, parentId); - switch(dtype){ - case(DvObject.DATAVERSE_DTYPE_STRING): + switch(DvObject.DType.valueOf(dtype)){ + case Dataverse: //if (this.idsWithDataversePermissions.containsKey(dvId)){ this.directDataverseIds.add(dvId); // Direct dataverse (no indirect dataverses) //} @@ -532,7 +532,7 @@ private boolean runStep2DirectAssignments(){ this.datasetParentIds.add(dvId); // Parent to dataset } break; - case(DvObject.DATASET_DTYPE_STRING): + case Dataset: //if (this.idsWithDatasetPermissions.containsKey(dvId)){ this.directDatasetIds.add(dvId); // Direct dataset //} @@ -540,7 +540,7 @@ private boolean runStep2DirectAssignments(){ this.fileParentIds.add(dvId); // Parent to file } break; - case(DvObject.DATAFILE_DTYPE_STRING): + case DataFile: if (this.idsWithFilePermissions.containsKey(dvId)){ this.directFileIds.add(dvId); // Direct file } @@ -585,7 +585,7 @@ private boolean runStep3FilePermsAssignedAtDataverse(){ this.childToParentIds.put(dvId, parentId); // Should ALWAYS be a Dataset! - if (dtype.equals(DvObject.DATASET_DTYPE_STRING)){ + if (DvObject.DType.valueOf(dtype).equals(DvObject.DType.Dataset)){ this.fileParentIds.add(dvId); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataPage.java b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataPage.java index 03acc0ccddc..d24bebbe948 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/MyDataPage.java @@ -1,19 +1,12 @@ package edu.harvard.iq.dataverse.mydata; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import edu.harvard.iq.dataverse.DatasetPage; import edu.harvard.iq.dataverse.DataverseRoleServiceBean; import edu.harvard.iq.dataverse.DataverseSession; -import edu.harvard.iq.dataverse.DvObject; -import static edu.harvard.iq.dataverse.DvObject.DATAFILE_DTYPE_STRING; -import static edu.harvard.iq.dataverse.DvObject.DATASET_DTYPE_STRING; -import static edu.harvard.iq.dataverse.DvObject.DATAVERSE_DTYPE_STRING; import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.PermissionsWrapper; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.search.SearchServiceBean; -import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.DataverseRolePermissionHelper; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/RolePermissionHelperPage.java b/src/main/java/edu/harvard/iq/dataverse/mydata/RolePermissionHelperPage.java index 06841c470d8..4c596d1bb84 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/RolePermissionHelperPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/RolePermissionHelperPage.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.DatasetPage; import edu.harvard.iq.dataverse.DataverseRoleServiceBean; import edu.harvard.iq.dataverse.DataverseSession; +import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.DataverseRolePermissionHelper; @@ -46,13 +47,6 @@ public String init() { List roleList = dataverseRoleService.findAll(); rolePermissionHelper = new DataverseRolePermissionHelper(roleList); - - List dtypes = MyDataFilterParams.defaultDvObjectTypes; - //List dtypes = Arrays.asList(DvObject.DATAFILE_DTYPE_STRING, DvObject.DATASET_DTYPE_STRING); - //DvObject.DATAFILE_DTYPE_STRING, DvObject.DATASET_DTYPE_STRING, DvObject.DATAVERSE_DTYPE_STRING - - //List dtypes = new ArrayList<>(); - return null; } diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/RoleTagRetriever.java b/src/main/java/edu/harvard/iq/dataverse/mydata/RoleTagRetriever.java index 4556c92ff19..cf7380a9a9b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/RoleTagRetriever.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/RoleTagRetriever.java @@ -346,7 +346,7 @@ private void findDataverseIdsForFiles(){ //msg("result: dvId: " + dvId + " |dtype: " + dtype + " |parentId: " + parentId); // Should ALWAYS be a Dataset! - if (dtype.equals(DvObject.DATASET_DTYPE_STRING)){ + if (DvObject.DType.valueOf(dtype).equals(DvObject.DType.Dataset)) { this.childToParentIdHash.put(dvId, parentId); // Store the parent child relation this.addIdNeedingRoleRetrieval(parentId); // We need the roles for this dataverse this.idToDvObjectType.put(parentId, SearchConstants.SOLR_DATAVERSES); // store the dv object type diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/FakePidProviderServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/FakePidProviderServiceBean.java index 58b765b31ab..7efc6441cac 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/FakePidProviderServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/FakePidProviderServiceBean.java @@ -1,68 +1,45 @@ package edu.harvard.iq.dataverse.pidproviders; -import edu.harvard.iq.dataverse.AbstractGlobalIdServiceBean; +import edu.harvard.iq.dataverse.DOIServiceBean; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.GlobalId; -import edu.harvard.iq.dataverse.engine.command.impl.CreateNewDatasetCommand; -import edu.harvard.iq.dataverse.engine.command.impl.ImportDatasetCommand; -import edu.harvard.iq.dataverse.util.FileUtil; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key; -import java.lang.StackWalker.StackFrame; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.logging.Logger; -import java.util.stream.Stream; +import javax.annotation.PostConstruct; import javax.ejb.Stateless; @Stateless -public class FakePidProviderServiceBean extends AbstractGlobalIdServiceBean { +public class FakePidProviderServiceBean extends DOIServiceBean { private static final Logger logger = Logger.getLogger(FakePidProviderServiceBean.class.getCanonicalName()); - @Override - public boolean alreadyExists(DvObject dvo) throws Exception { - /* - * This method is called in cases where the 'right' answer can be true or false: - * - * When called via CreateNewDatasetCommand (direct upload case), we expect - * 'false' as the response, whereas when called from ImportDatasetCommand or - * DeleteDataFileCommand, we expect 'true' as a confirmation that the expected - * PID exists. - * - * This method now checks the stack and can send true/false as expected by the - * calling command as the right default/normal case. - * - * Alternately, this method could check the database as is done in - * DatasetServiceBean.isIdentifierLocallyUnique() (needs a similar method for - * DataFiles and could be refactored to only have one query for both). - */ - StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - if (walker.walk(this::getCallingClass)) { - logger.fine("Called from CreateNewDatasetCommand"); - return false; + @PostConstruct + private void init() { + String doiProvider = settingsService.getValueForKey(Key.DoiProvider, ""); + if("FAKE".equals(doiProvider)) { + configured=true; } - return true; } - - private boolean getCallingClass(Stream stackFrameStream) { - /* - * If/when other cases require a true response from the alreadyExists method, - * add those class names to the test below. - */ - return stackFrameStream - .filter(frame -> frame.getDeclaringClass().getSimpleName() - .equals(CreateNewDatasetCommand.class.getSimpleName())) - .findFirst().map(f -> true).orElse(false); + + //Only need to check locally + public boolean isGlobalIdUnique(GlobalId globalId) { + try { + return ! alreadyExists(globalId); + } catch (Exception e){ + //we can live with failure - means identifier not found remotely + } + return true; } @Override public boolean alreadyExists(GlobalId globalId) throws Exception { - //Could use the same method as above to return false if/when needed. - return true; + return ! dvObjectService.isGlobalIdLocallyUnique(globalId); } @Override @@ -101,12 +78,6 @@ public void deleteIdentifier(DvObject dvo) throws Exception { // no-op } - @Override - public Map lookupMetadataFromIdentifier(String protocol, String authority, String identifier) { - Map map = new HashMap<>(); - return map; - } - @Override public boolean publicizeIdentifier(DvObject studyIn) { return true; diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PermaLinkPidProviderServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PermaLinkPidProviderServiceBean.java new file mode 100644 index 00000000000..620ab3ce052 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PermaLinkPidProviderServiceBean.java @@ -0,0 +1,161 @@ +package edu.harvard.iq.dataverse.pidproviders; + +import edu.harvard.iq.dataverse.AbstractGlobalIdServiceBean; +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.GlobalIdServiceBean; +import edu.harvard.iq.dataverse.engine.command.impl.CreateNewDatasetCommand; +import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key; +import edu.harvard.iq.dataverse.util.SystemConfig; + +import java.lang.StackWalker.StackFrame; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import javax.annotation.PostConstruct; +import javax.ejb.Stateless; + +/** + * PermaLink provider + * This is a minimalist permanent ID provider intended for use with 'real' datasets/files where the use case none-the-less doesn't lend itself to the use of DOIs or Handles, e.g. + * * due to cost + * * for a catalog/archive where Dataverse has a dataset representing a dataset with DOI/handle stored elsewhere + * + * The initial implementation will mint identifiers locally and will provide the existing page URLs (using the ?persistentID= format). + * This will be overridable by a configurable parameter to support use of an external resolver. + * + */ +@Stateless +public class PermaLinkPidProviderServiceBean extends AbstractGlobalIdServiceBean { + + private static final Logger logger = Logger.getLogger(PermaLinkPidProviderServiceBean.class.getCanonicalName()); + + public static final String PERMA_PROTOCOL = "perma"; + public static final String PERMA_PROVIDER_NAME = "PERMA"; + + //ToDo - handle dataset/file defaults for local system + public static final String PERMA_RESOLVER_URL = System.getProperty("perma.baseurlstring", SystemConfig.getDataverseSiteUrlStatic()); + + String authority = null; + private String separator = ""; + + @PostConstruct + private void init() { + if(PERMA_PROTOCOL.equals(settingsService.getValueForKey(Key.Protocol))){ + authority = settingsService.getValueForKey(Key.Authority); + configured=true; + }; + + } + + + //Only used in PidUtilTest - haven't figured out how to mock a PostConstruct call directly + // ToDo - remove after work to allow more than one Pid Provider which is expected to not use stateless beans + public void reInit() { + init(); + } + + @Override + public String getSeparator() { + //The perma default + return separator; + } + + @Override + public boolean alreadyExists(GlobalId globalId) throws Exception { + return ! dvObjectService.isGlobalIdLocallyUnique(globalId); + } + + @Override + public boolean registerWhenPublished() { + return false; + } + + @Override + public List getProviderInformation() { + ArrayList providerInfo = new ArrayList<>(); + providerInfo.add(PERMA_PROVIDER_NAME); + providerInfo.add(PERMA_RESOLVER_URL); + return providerInfo; + } + + @Override + public String createIdentifier(DvObject dvo) throws Throwable { + //Call external resolver and send landing URL? + //FWIW: Return value appears to only be used in RegisterDvObjectCommand where success requires finding the dvo identifier in this string. (Also logged a couple places). + return(dvo.getGlobalId().asString()); + } + + @Override + public Map getIdentifierMetadata(DvObject dvo) { + Map map = new HashMap<>(); + return map; + } + + @Override + public String modifyIdentifierTargetURL(DvObject dvo) throws Exception { + return getTargetUrl(dvo); + } + + @Override + public void deleteIdentifier(DvObject dvo) throws Exception { + // no-op + } + + @Override + public boolean publicizeIdentifier(DvObject dvObject) { + //Generate if needed (i.e. datafile case where we don't create/register early (even with reigsterWhenPublished == false)) + if(dvObject.getIdentifier() == null || dvObject.getIdentifier().isEmpty() ){ + dvObject = generateIdentifier(dvObject); + } + //Call external resolver and send landing URL? + return true; + } + + @Override + public GlobalId parsePersistentId(String pidString) { + //ToDo - handle local PID resolver for dataset/file + if (pidString.startsWith(getUrlPrefix())) { + pidString = pidString.replace(getUrlPrefix(), + (PERMA_PROTOCOL + ":")); + } + return super.parsePersistentId(pidString); + } + + @Override + public GlobalId parsePersistentId(String protocol, String identifierString) { + logger.info("Checking Perma: " + identifierString); + if (!PERMA_PROTOCOL.equals(protocol)) { + return null; + } + String identifier = null; + if (authority != null) { + if (identifierString.startsWith(authority)) { + identifier = identifierString.substring(authority.length()); + } + } + identifier = GlobalIdServiceBean.formatIdentifierString(identifier); + if (GlobalIdServiceBean.testforNullTerminator(identifier)) { + return null; + } + return new GlobalId(PERMA_PROTOCOL, authority, identifier, separator, getUrlPrefix(), PERMA_PROVIDER_NAME); + } + + @Override + public GlobalId parsePersistentId(String protocol, String authority, String identifier) { + if (!PERMA_PROTOCOL.equals(protocol)) { + return null; + } + return super.parsePersistentId(protocol, authority, identifier); + } + + @Override + public String getUrlPrefix() { + + return PERMA_RESOLVER_URL + "/citation?persistentId=" + PERMA_PROTOCOL + ":"; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidHelper.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidHelper.java new file mode 100644 index 00000000000..478f5d6c2c4 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidHelper.java @@ -0,0 +1,43 @@ +package edu.harvard.iq.dataverse.pidproviders; + +import java.util.Arrays; +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.ejb.Singleton; +import javax.ejb.Startup; + +import edu.harvard.iq.dataverse.DOIDataCiteServiceBean; +import edu.harvard.iq.dataverse.DOIEZIdServiceBean; +import edu.harvard.iq.dataverse.HandlenetServiceBean; + + /** + * This is a small helper bean + * As it is a singleton and built at application start (=deployment), it will inject the (stateless) + * dataverse service into the BrandingUtil once it's ready. + */ + @Startup + @Singleton + public class PidHelper { + + @EJB + DOIDataCiteServiceBean datacitePidSvc; + @EJB + DOIEZIdServiceBean ezidPidSvc; + @EJB + HandlenetServiceBean handlePidSvc; + @EJB + FakePidProviderServiceBean fakePidSvc; + @EJB + PermaLinkPidProviderServiceBean permaPidSvc; + @EJB + UnmanagedDOIServiceBean unmanagedDOISvc; + @EJB + UnmanagedHandlenetServiceBean unmanagedHandleSvc; + + @PostConstruct + public void listServices() { + PidUtil.addAllToProviderList(Arrays.asList(datacitePidSvc, ezidPidSvc, handlePidSvc, permaPidSvc, fakePidSvc)); + PidUtil.addAllToUnmanagedProviderList(Arrays.asList(unmanagedDOISvc, unmanagedHandleSvc)); + } + + } \ No newline at end of file diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidUtil.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidUtil.java index f9b451ecab2..4db7d099a47 100644 --- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/PidUtil.java @@ -1,16 +1,21 @@ package edu.harvard.iq.dataverse.pidproviders; +import edu.harvard.iq.dataverse.DOIServiceBean; import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.GlobalIdServiceBean; +import edu.harvard.iq.dataverse.HandlenetServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; import java.net.URL; import java.util.Arrays; import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Logger; + import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; @@ -18,32 +23,32 @@ import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.NotFoundException; import javax.ws.rs.ServiceUnavailableException; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; public class PidUtil { private static final Logger logger = Logger.getLogger(PidUtil.class.getCanonicalName()); /** - * @throws BadRequestException if user didn't supply a DOI. + * @throws BadRequestException if user didn't supply a DOI. * - * @throws NotFoundException if DOI not found in DataCite. + * @throws NotFoundException if DOI not found in DataCite. * - * @throws ServiceUnavailableException if non 200 or non 404 response from - * DataCite. + * @throws ServiceUnavailableException if non 200 or non 404 response from + * DataCite. * * @throws InternalServerErrorException on local misconfiguration such as - * DataCite hostname not in DNS. + * DataCite hostname not in DNS. */ - public static JsonObjectBuilder queryDoi(String persistentId, String baseUrl, String username, String password) { + public static JsonObjectBuilder queryDoi(GlobalId globalId, String baseUrl, String username, String password) { try { // This throws an exception if this is not a DOI, which is the only // user-supplied param - treat this as a BadRequest in the catch statement. - String doi = acceptOnlyDoi(persistentId); + String doi = acceptOnlyDoi(globalId); URL url; - // Other errors are all internal misconfiguration (any problems creating the URL), the - // DOI doesn't exist (404 from DataCite), or problem at DataCite (other non-200 responses). + // Other errors are all internal misconfiguration (any problems creating the + // URL), the + // DOI doesn't exist (404 from DataCite), or problem at DataCite (other non-200 + // responses). int status = 0; HttpURLConnection connection = null; try { @@ -63,17 +68,20 @@ public static JsonObjectBuilder queryDoi(String persistentId, String baseUrl, St BundleUtil.getStringFromBundle("pids.datacite.errors.noResponseCode", Arrays.asList(baseUrl))); } if (status == 404) { - //Could check to see if Dataverse expects the DOI to be registered - that would result in a 404 from Dataverse before having to contact DataCite, and DataCite could still return a 404 - throw new NotFoundException("404 (NOT FOUND) from DataCite for DOI " + persistentId); + // Could check to see if Dataverse expects the DOI to be registered - that would + // result in a 404 from Dataverse before having to contact DataCite, and + // DataCite could still return a 404 + throw new NotFoundException("404 (NOT FOUND) from DataCite for DOI " + globalId); } if (status != 200) { - /* We could just send back whatever status code DataCite sends, but we've seen + /* + * We could just send back whatever status code DataCite sends, but we've seen * DataCite sometimes respond with 403 when the credentials were OK, and their - * 500 error doesn't mean a problem with Dataverse, so wrapping any of them in - * a 503 error, to indicate this is a temporary error, might be the better option. In any case, we need to log the - * issue to be able to debug it. + * 500 error doesn't mean a problem with Dataverse, so wrapping any of them in a + * 503 error, to indicate this is a temporary error, might be the better option. + * In any case, we need to log the issue to be able to debug it. */ - logger.severe("Received " + status + " error from DataCite for DOI: " + persistentId); + logger.severe("Received " + status + " error from DataCite for DOI: " + globalId); InputStream errorStream = connection.getErrorStream(); if (errorStream != null) { JsonObject out = Json.createReader(connection.getErrorStream()).readObject(); @@ -104,11 +112,107 @@ public static JsonObjectBuilder queryDoi(String persistentId, String baseUrl, St * @param PID in the form doi:10.7910/DVN/TJCLKP * @return DOI in the form 10.7910/DVN/TJCLKP (no "doi:") */ - private static String acceptOnlyDoi(String persistentId) { - GlobalId globalId = new GlobalId(persistentId); - if (!GlobalId.DOI_PROTOCOL.equals(globalId.getProtocol())) { + private static String acceptOnlyDoi(GlobalId globalId) { + if (!DOIServiceBean.DOI_PROTOCOL.equals(globalId.getProtocol())) { throw new IllegalArgumentException(BundleUtil.getStringFromBundle("pids.datacite.errors.DoiOnly")); } return globalId.getAuthority() + "/" + globalId.getIdentifier(); } + + static Map providerMap = new HashMap(); + static Map unmanagedProviderMap = new HashMap(); + + public static void addAllToProviderList(List list) { + for (GlobalIdServiceBean pidProvider : list) { + providerMap.put(pidProvider.getProviderInformation().get(0), pidProvider); + } + } + + public static void addAllToUnmanagedProviderList(List list) { + for (GlobalIdServiceBean pidProvider : list) { + unmanagedProviderMap.put(pidProvider.getProviderInformation().get(0), pidProvider); + } + } + + /** + * + * @param identifier The string to be parsed + * @throws IllegalArgumentException if the passed string cannot be parsed. + */ + public static GlobalId parseAsGlobalID(String identifier) { + logger.fine("In parseAsGlobalId: " + providerMap.size()); + for (GlobalIdServiceBean pidProvider : providerMap.values()) { + logger.fine(" Checking " + String.join(",", pidProvider.getProviderInformation())); + GlobalId globalId = pidProvider.parsePersistentId(identifier); + if (globalId != null) { + return globalId; + } + } + // If no providers can managed this PID, at least allow it to be recognized + for (GlobalIdServiceBean pidProvider : unmanagedProviderMap.values()) { + logger.fine(" Checking " + String.join(",", pidProvider.getProviderInformation())); + GlobalId globalId = pidProvider.parsePersistentId(identifier); + if (globalId != null) { + return globalId; + } + } + throw new IllegalArgumentException("Failed to parse identifier: " + identifier); + } + + /** + * + * @param identifier The string to be parsed + * @throws IllegalArgumentException if the passed string cannot be parsed. + */ + public static GlobalId parseAsGlobalID(String protocol, String authority, String identifier) { + logger.fine("Looking for " + protocol + " " + authority + " " + identifier); + logger.fine("In parseAsGlobalId: " + providerMap.size()); + for (GlobalIdServiceBean pidProvider : providerMap.values()) { + logger.fine(" Checking " + String.join(",", pidProvider.getProviderInformation())); + GlobalId globalId = pidProvider.parsePersistentId(protocol, authority, identifier); + if (globalId != null) { + return globalId; + } + } + for (GlobalIdServiceBean pidProvider : unmanagedProviderMap.values()) { + logger.fine(" Checking " + String.join(",", pidProvider.getProviderInformation())); + GlobalId globalId = pidProvider.parsePersistentId(protocol, authority, identifier); + if (globalId != null) { + return globalId; + } + } + // For unit tests which don't have any provider Beans - todo remove when + // providers are no longer beans and can be configured easily in tests + return parseUnmanagedDoiOrHandle(protocol, authority, identifier); + // throw new IllegalArgumentException("Failed to parse identifier from protocol: + // " + protocol + ", authority:" + authority + ", identifier: " + identifier); + } + /* + * This method should be deprecated/removed when further refactoring to support + * multiple PID providers is done. At that point, when the providers aren't + * beans, this code can be moved into other classes that go in the providerMap. + * If this method is not kept in sync with the DOIServiceBean and + * HandlenetServiceBean implementations, the tests using it won't be valid tests + * of the production code. + */ + + private static GlobalId parseUnmanagedDoiOrHandle(String protocol, String authority, String identifier) { + // Default recognition - could be moved to new classes in the future. + if (!GlobalIdServiceBean.isValidGlobalId(protocol, authority, identifier)) { + return null; + } + String urlPrefix = null; + switch (protocol) { + case DOIServiceBean.DOI_PROTOCOL: + if (!GlobalIdServiceBean.checkDOIAuthority(authority)) { + return null; + } + urlPrefix = DOIServiceBean.DOI_RESOLVER_URL; + break; + case HandlenetServiceBean.HDL_PROTOCOL: + urlPrefix = HandlenetServiceBean.HDL_RESOLVER_URL; + break; + } + return new GlobalId(protocol, authority, identifier, "/", urlPrefix, null); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedDOIServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedDOIServiceBean.java new file mode 100644 index 00000000000..e0b7dabfb29 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedDOIServiceBean.java @@ -0,0 +1,88 @@ +package edu.harvard.iq.dataverse.pidproviders; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import javax.annotation.PostConstruct; +import javax.ejb.Stateless; + +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.lang3.NotImplementedException; + +import edu.harvard.iq.dataverse.DOIServiceBean; +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.GlobalId; + +/** This class is just used to parse DOIs that are not managed by any account configured in Dataverse + * It does not implement any of the methods related to PID CRUD + * + */ + +@Stateless +public class UnmanagedDOIServiceBean extends DOIServiceBean { + + private static final Logger logger = Logger.getLogger(UnmanagedDOIServiceBean.class.getCanonicalName()); + + @PostConstruct + private void init() { + // Always on + configured = true; + } + + @Override + public boolean canManagePID() { + return false; + } + + @Override + public boolean registerWhenPublished() { + return false; + } + + @Override + public boolean alreadyExists(GlobalId pid) { + return false; + } + + @Override + public String createIdentifier(DvObject dvObject) throws Exception { + throw new NotImplementedException(); + } + + @Override + public Map getIdentifierMetadata(DvObject dvObject) { + throw new NotImplementedException(); + } + + @Override + public String modifyIdentifierTargetURL(DvObject dvObject) throws Exception { + throw new NotImplementedException(); + } + + @Override + public void deleteIdentifier(DvObject dvObject) throws IOException, HttpException { + throw new NotImplementedException(); + } + + @Override + public boolean publicizeIdentifier(DvObject dvObject) { + throw new NotImplementedException(); + } + + @Override + public List getProviderInformation() { + ArrayList providerInfo = new ArrayList<>(); + String providerName = "UnmanagedDOIProvider"; + String providerLink = ""; + providerInfo.add(providerName); + providerInfo.add(providerLink); + return providerInfo; + } + + // PID recognition + // Done by DOIServiceBean + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedHandlenetServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedHandlenetServiceBean.java new file mode 100644 index 00000000000..590fd5b67cd --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/UnmanagedHandlenetServiceBean.java @@ -0,0 +1,110 @@ +package edu.harvard.iq.dataverse.pidproviders; + +import edu.harvard.iq.dataverse.AbstractGlobalIdServiceBean; +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.GlobalId; +import edu.harvard.iq.dataverse.HandlenetServiceBean; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.Stateless; +import org.apache.commons.lang3.NotImplementedException; + +/** This class is just used to parse Handles that are not managed by any account configured in Dataverse + * It does not implement any of the methods related to PID CRUD + * + */ +@Stateless +public class UnmanagedHandlenetServiceBean extends AbstractGlobalIdServiceBean { + + private static final Logger logger = Logger.getLogger(UnmanagedHandlenetServiceBean.class.getCanonicalName()); + + public UnmanagedHandlenetServiceBean() { + logger.log(Level.FINE, "Constructor"); + configured = true; + } + + @Override + public boolean canManagePID() { + return false; + } + + @Override + public boolean registerWhenPublished() { + throw new NotImplementedException(); + } + + @Override + public boolean alreadyExists(GlobalId pid) throws Exception { + throw new NotImplementedException(); + } + + @Override + public Map getIdentifierMetadata(DvObject dvObject) { + throw new NotImplementedException(); + } + + @Override + public String modifyIdentifierTargetURL(DvObject dvObject) throws Exception { + throw new NotImplementedException(); + } + + @Override + public void deleteIdentifier(DvObject dvObject) throws Exception { + throw new NotImplementedException(); + } + + @Override + public List getProviderInformation() { + ArrayList providerInfo = new ArrayList<>(); + String providerName = "UnmanagedHandle"; + String providerLink = ""; + providerInfo.add(providerName); + providerInfo.add(providerLink); + return providerInfo; + } + + @Override + public String createIdentifier(DvObject dvObject) throws Throwable { + throw new NotImplementedException(); + } + + @Override + public boolean publicizeIdentifier(DvObject dvObject) { + throw new NotImplementedException(); + } + + @Override + public GlobalId parsePersistentId(String pidString) { + if (pidString.startsWith(HandlenetServiceBean.HDL_RESOLVER_URL)) { + pidString = pidString.replace(HandlenetServiceBean.HDL_RESOLVER_URL, + (HandlenetServiceBean.HDL_PROTOCOL + ":")); + } else if (pidString.startsWith(HandlenetServiceBean.HTTP_HDL_RESOLVER_URL)) { + pidString = pidString.replace(HandlenetServiceBean.HTTP_HDL_RESOLVER_URL, + (HandlenetServiceBean.HDL_PROTOCOL + ":")); + } + return super.parsePersistentId(pidString); + } + + @Override + public GlobalId parsePersistentId(String protocol, String identifierString) { + if (!HandlenetServiceBean.HDL_PROTOCOL.equals(protocol)) { + return null; + } + GlobalId globalId = super.parsePersistentId(protocol, identifierString); + return globalId; + } + + @Override + public GlobalId parsePersistentId(String protocol, String authority, String identifier) { + if (!HandlenetServiceBean.HDL_PROTOCOL.equals(protocol)) { + return null; + } + return super.parsePersistentId(protocol, authority, identifier); + } + + @Override + public String getUrlPrefix() { + return HandlenetServiceBean.HDL_RESOLVER_URL; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtil.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtil.java index c363139c912..e9d95e2faf4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtil.java @@ -130,7 +130,7 @@ static String getDraftDatasetPageToBeRedirectedTo(RoleAssignment roleAssignment) static String getDraftUrl(DatasetVersion draft) { if (draft != null) { Dataset dataset = draft.getDataset(); - if (dataset != null) { + if (dataset != null && dataset.getGlobalId()!=null) { if ( dataset.getGlobalId().isComplete() ) { String relativeUrl = "/dataset.xhtml?persistentId=" + dataset.getGlobalId().toString() + "&version=DRAFT"; return relativeUrl; diff --git a/src/main/java/edu/harvard/iq/dataverse/search/IndexServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/search/IndexServiceBean.java index e73cce8acbe..bd93bc15b2f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/search/IndexServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/search/IndexServiceBean.java @@ -20,6 +20,7 @@ import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.Embargo; import edu.harvard.iq.dataverse.FileMetadata; +import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.PermissionServiceBean; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean; @@ -1296,7 +1297,9 @@ public SolrInputDocuments toSolrDocs(IndexableDataset indexableDataset, Set maybePid = GlobalId.parse(jsonld.getString("@id")); + Optional maybePid = GlobalIdServiceBean.parse(jsonld.getString("@id")); if (maybePid.isPresent()) { ds.setGlobalId(maybePid.get()); } else { diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 9f5401f77d1..6ec4209336d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -625,18 +625,15 @@ public static JsonObjectBuilder json(DataFile df, FileMetadata fileMetadata) { } fileName = fileMetadata.getLabel(); - - String pidURL = ""; - - if (new GlobalId(df).toURL() != null){ - pidURL = new GlobalId(df).toURL().toString(); - } + GlobalId filePid = df.getGlobalId(); + String pidURL = (filePid!=null)? filePid.asURL(): null; + String pidString = (filePid!=null)? filePid.asString(): null; JsonObjectBuilder embargo = df.getEmbargo() != null ? JsonPrinter.json(df.getEmbargo()) : null; return jsonObjectBuilder() .add("id", df.getId()) - .add("persistentId", df.getGlobalIdString()) + .add("persistentId", pidString) .add("pidURL", pidURL) .add("filename", fileName) .add("contentType", df.getContentType()) diff --git a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/HttpSendReceiveClientStep.java b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/HttpSendReceiveClientStep.java index 08964c78137..93ee5e60c9b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/HttpSendReceiveClientStep.java +++ b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/HttpSendReceiveClientStep.java @@ -109,7 +109,7 @@ HttpMethodBase buildMethod(boolean rollback, WorkflowContext ctxt) throws Except templateParams.put( "invocationId", ctxt.getInvocationId() ); templateParams.put( "dataset.id", Long.toString(ctxt.getDataset().getId()) ); templateParams.put( "dataset.identifier", ctxt.getDataset().getIdentifier() ); - templateParams.put( "dataset.globalId", ctxt.getDataset().getGlobalIdString() ); + templateParams.put( "dataset.globalId", ctxt.getDataset().getGlobalId().asString() ); templateParams.put( "dataset.displayName", ctxt.getDataset().getDisplayName() ); templateParams.put( "dataset.citation", ctxt.getDataset().getCitation() ); templateParams.put( "minorVersion", Long.toString(ctxt.getNextMinorVersionNumber()) ); diff --git a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/LDNAnnounceDatasetVersionStep.java b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/LDNAnnounceDatasetVersionStep.java index 3478d9398f0..13024f9f68f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/LDNAnnounceDatasetVersionStep.java +++ b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/LDNAnnounceDatasetVersionStep.java @@ -216,7 +216,7 @@ HttpPost buildAnnouncement(boolean qb, WorkflowContext ctxt, JsonObject target) Dataset d = ctxt.getDataset(); job.add("object", Json.createObjectBuilder().add("id", d.getLocalURL()) - .add("ietf:cite-as", d.getGlobalId().toURL().toExternalForm()) + .add("ietf:cite-as", d.getGlobalId().asURL()) .add("sorg:name", d.getDisplayName()).add("type", "sorg:Dataset")); job.add("origin", Json.createObjectBuilder().add("id", SystemConfig.getDataverseSiteUrlStatic()) .add("inbox", SystemConfig.getDataverseSiteUrlStatic() + "/api/inbox").add("type", "Service")); diff --git a/src/main/webapp/dataset-versions.xhtml b/src/main/webapp/dataset-versions.xhtml index b1612a314fc..f3bcd6007fa 100644 --- a/src/main/webapp/dataset-versions.xhtml +++ b/src/main/webapp/dataset-versions.xhtml @@ -11,7 +11,7 @@

- + @@ -59,11 +59,11 @@ - + - + diff --git a/src/main/webapp/dataset-widgets.xhtml b/src/main/webapp/dataset-widgets.xhtml index 93072952a36..f635099dfdb 100644 --- a/src/main/webapp/dataset-widgets.xhtml +++ b/src/main/webapp/dataset-widgets.xhtml @@ -139,7 +139,7 @@

- +
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml index 6b91f815d9a..11b449e815e 100644 --- a/src/main/webapp/dataset.xhtml +++ b/src/main/webapp/dataset.xhtml @@ -761,7 +761,7 @@ - + @@ -902,7 +902,7 @@ - + diff --git a/src/main/webapp/dataverse_header.xhtml b/src/main/webapp/dataverse_header.xhtml index 8ae117dd869..30818b9d683 100644 --- a/src/main/webapp/dataverse_header.xhtml +++ b/src/main/webapp/dataverse_header.xhtml @@ -268,7 +268,7 @@
- + diff --git a/src/main/webapp/file-info-fragment.xhtml b/src/main/webapp/file-info-fragment.xhtml index 8d30f0e2179..33a8d2c3ca5 100644 --- a/src/main/webapp/file-info-fragment.xhtml +++ b/src/main/webapp/file-info-fragment.xhtml @@ -47,7 +47,7 @@
- + #{fileMetadata.label} diff --git a/src/main/webapp/file-versions.xhtml b/src/main/webapp/file-versions.xhtml index f4d6932485c..f7f259ce2e0 100644 --- a/src/main/webapp/file-versions.xhtml +++ b/src/main/webapp/file-versions.xhtml @@ -11,7 +11,7 @@

- + diff --git a/src/main/webapp/file.xhtml b/src/main/webapp/file.xhtml index 6196780aa82..ae8729fdf89 100644 --- a/src/main/webapp/file.xhtml +++ b/src/main/webapp/file.xhtml @@ -609,7 +609,7 @@

#{bundle['file.share.tip']}

-
+