package edu.harvard.iq.dataverse.engine.command.impl;
import edu.harvard.iq.dataverse.DataFile;
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetVersionUser;
import edu.harvard.iq.dataverse.DatasetField;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.DatasetVersion.VersionState;
import edu.harvard.iq.dataverse.RoleAssignment;
import edu.harvard.iq.dataverse.Template;
import edu.harvard.iq.dataverse.api.imports.ImportUtil;
import edu.harvard.iq.dataverse.api.imports.ImportUtil.ImportType;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.engine.command.AbstractCommand;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.validation.ConstraintViolation;
/**
* Creates a {@link Dataset} in the passed {@link CommandContext}.
*
* @author michael
*/
@RequiredPermissions(Permission.AddDataset)
public class CreateDatasetCommand extends AbstractCommand<Dataset> {
private static final Logger logger = Logger.getLogger(CreateDatasetCommand.class.getCanonicalName());
private final Dataset theDataset;
private final boolean registrationRequired;
// TODO: rather than have a boolean, create a sub-command for creating a dataset during import
private final ImportUtil.ImportType importType;
private final Template template;
public CreateDatasetCommand(Dataset theDataset, DataverseRequest aRequest) {
super(aRequest, theDataset.getOwner());
this.theDataset = theDataset;
this.registrationRequired = false;
this.importType=null;
this.template=null;
}
public CreateDatasetCommand(Dataset theDataset, DataverseRequest aRequest, boolean registrationRequired) {
super(aRequest, theDataset.getOwner());
this.theDataset = theDataset;
this.registrationRequired = registrationRequired;
this.importType=null;
this.template=null;
}
public CreateDatasetCommand(Dataset theDataset, DataverseRequest aRequest, boolean registrationRequired, ImportUtil.ImportType importType) {
super(aRequest, theDataset.getOwner());
this.theDataset = theDataset;
this.registrationRequired = registrationRequired;
this.importType=importType;
this.template=null;
}
public CreateDatasetCommand(Dataset theDataset, DataverseRequest aRequest, boolean registrationRequired, ImportUtil.ImportType importType, Template template) {
super(aRequest, theDataset.getOwner());
this.theDataset = theDataset;
this.registrationRequired = registrationRequired;
this.importType=importType;
this.template=template;
}
@Override
public Dataset execute(CommandContext ctxt) throws CommandException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-hh.mm.ss");
if ( (importType != ImportType.MIGRATION && importType != ImportType.HARVEST) && !ctxt.datasets().isUniqueIdentifier(theDataset.getIdentifier(), theDataset.getProtocol(), theDataset.getAuthority(), theDataset.getDoiSeparator()) ) {
throw new IllegalCommandException(String.format("Dataset with identifier '%s', protocol '%s' and authority '%s' already exists",
theDataset.getIdentifier(), theDataset.getProtocol(), theDataset.getAuthority()),
this);
}
// If we are importing with the API, then we don't want to create an editable version,
// just save the version is already in theDataset.
DatasetVersion dsv = importType!=null? theDataset.getLatestVersion() : theDataset.getEditVersion();
// validate
// @todo for now we run through an initFields method that creates empty fields for anything without a value
// that way they can be checked for required
dsv.setDatasetFields(dsv.initDatasetFields());
Set<ConstraintViolation> constraintViolations = dsv.validate();
if (!constraintViolations.isEmpty()) {
String validationFailedString = "Validation failed:";
for (ConstraintViolation constraintViolation : constraintViolations) {
validationFailedString += " " + constraintViolation.getMessage();
validationFailedString += " Invalid value: '" + constraintViolation.getInvalidValue() + "'.";
}
throw new IllegalCommandException(validationFailedString, this);
}
theDataset.setCreator((AuthenticatedUser) getRequest().getUser());
theDataset.setCreateDate(new Timestamp(new Date().getTime()));
Iterator<DatasetField> dsfIt = dsv.getDatasetFields().iterator();
while (dsfIt.hasNext()) {
if (dsfIt.next().removeBlankDatasetFieldValues()) {
dsfIt.remove();
}
}
Iterator<DatasetField> dsfItSort = dsv.getDatasetFields().iterator();
while (dsfItSort.hasNext()) {
dsfItSort.next().setValueDisplayOrder();
}
Timestamp createDate = new Timestamp(new Date().getTime());
dsv.setCreateTime(createDate);
dsv.setLastUpdateTime(createDate);
theDataset.setModificationTime(createDate);
for (DataFile dataFile: theDataset.getFiles() ){
dataFile.setCreator((AuthenticatedUser) getRequest().getUser());
dataFile.setCreateDate(theDataset.getCreateDate());
}
String nonNullDefaultIfKeyNotFound = "";
String protocol = ctxt.settings().getValueForKey(SettingsServiceBean.Key.Protocol, nonNullDefaultIfKeyNotFound);
String authority = ctxt.settings().getValueForKey(SettingsServiceBean.Key.Authority, nonNullDefaultIfKeyNotFound);
String doiSeparator = ctxt.settings().getValueForKey(SettingsServiceBean.Key.DoiSeparator, nonNullDefaultIfKeyNotFound);
String doiProvider = ctxt.settings().getValueForKey(SettingsServiceBean.Key.DoiProvider, nonNullDefaultIfKeyNotFound);
if (theDataset.getProtocol()==null) theDataset.setProtocol(protocol);
if (theDataset.getAuthority()==null) theDataset.setAuthority(authority);
if (theDataset.getDoiSeparator()==null) theDataset.setDoiSeparator(doiSeparator);
if (theDataset.getIdentifier()==null) {
theDataset.setIdentifier(ctxt.datasets().generateIdentifierSequence(theDataset.getProtocol(), theDataset.getAuthority(), theDataset.getDoiSeparator()));
}
// Attempt the registration if importing dataset through the API, or the app (but not harvest or migrate)
if ((importType == null || importType.equals(ImportType.NEW))
&& theDataset.getGlobalIdCreateTime() == null) {
if (protocol.equals("doi")) {
String doiRetString = "";
if (doiProvider.equals("EZID")) {
doiRetString = ctxt.doiEZId().createIdentifier(theDataset);
}
if (doiProvider.equals("DataCite")) {
try{
doiRetString = ctxt.doiDataCite().createIdentifier(theDataset);
} catch (Exception e){
logger.log(Level.WARNING, "Exception while creating Identifier:" + e.getMessage(), e);
}
}
// Check return value to make sure registration succeeded
if (doiProvider.equals("EZID") && doiRetString.contains(theDataset.getIdentifier())) {
theDataset.setGlobalIdCreateTime(createDate);
}
}
} else // If harvest or migrate, and this is a released dataset, we don't need to register,
// so set the globalIdCreateTime to now
if (theDataset.getLatestVersion().getVersionState().equals(VersionState.RELEASED)) {
theDataset.setGlobalIdCreateTime(new Date());
}
if (registrationRequired && theDataset.getGlobalIdCreateTime() == null) {
throw new IllegalCommandException("Dataset could not be created. Registration failed", this);
}
logger.log(Level.FINE, "after doi {0}", formatter.format(new Date().getTime()));
Dataset savedDataset = ctxt.em().merge(theDataset);
logger.log(Level.FINE, "after db update {0}", formatter.format(new Date().getTime()));
// set the role to be default contributor role for its dataverse
if (importType==null || importType.equals(ImportType.NEW)) {
String privateUrlToken = null;
ctxt.roles().save(new RoleAssignment(savedDataset.getOwner().getDefaultContributorRole(), getRequest().getUser(), savedDataset, privateUrlToken));
}
savedDataset.setPermissionModificationTime(new Timestamp(new Date().getTime()));
savedDataset = ctxt.em().merge(savedDataset);
if(template != null){
ctxt.templates().incrementUsageCount(template.getId());
}
try {
/**
* @todo Do something with the result. Did it succeed or fail?
*/
boolean doNormalSolrDocCleanUp = true;
ctxt.index().indexDataset(savedDataset, doNormalSolrDocCleanUp);
} catch ( Exception e ) { // RuntimeException e ) {
logger.log(Level.WARNING, "Exception while indexing:" + e.getMessage()); //, e);
/**
* Even though the original intention appears to have been to allow the
* dataset to be successfully created, even if an exception is thrown during
* the indexing - in reality, a runtime exception there, even caught,
* still forces the EJB transaction to be rolled back; hence the
* dataset is NOT created... but the command completes and exits as if
* it has been successful.
* So I am going to throw a Command Exception here, to avoid this.
* If we DO want to be able to create datasets even if they cannot
* be immediately indexed, we'll have to figure out how to do that.
* (Note that import is still possible when Solr is down - because indexDataset()
* does NOT throw an exception if it is.
* -- L.A. 4.5
*/
throw new CommandException("Dataset could not be created. Indexing failed", this);
}
logger.log(Level.FINE, "after index {0}", formatter.format(new Date().getTime()));
// if we are not migrating, assign the user to this version
if (importType==null || importType.equals(ImportType.NEW)) {
DatasetVersionUser datasetVersionDataverseUser = new DatasetVersionUser();
String id = getRequest().getUser().getIdentifier();
id = id.startsWith("@") ? id.substring(1) : id;
AuthenticatedUser au = ctxt.authentication().getAuthenticatedUser(id);
datasetVersionDataverseUser.setAuthenticatedUser(au);
datasetVersionDataverseUser.setDatasetVersion(savedDataset.getLatestVersion());
datasetVersionDataverseUser.setLastUpdateDate(createDate);
if (savedDataset.getLatestVersion().getId() == null){
logger.warning("CreateDatasetCommand: savedDataset version id is null");
} else {
datasetVersionDataverseUser.setDatasetVersion(savedDataset.getLatestVersion());
}
ctxt.em().merge(datasetVersionDataverseUser);
}
logger.log(Level.FINE,"after create version user " + formatter.format(new Date().getTime()));
return savedDataset;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.theDataset);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof CreateDatasetCommand)) {
return false;
}
final CreateDatasetCommand other = (CreateDatasetCommand) obj;
return Objects.equals(this.theDataset, other.theDataset);
}
@Override
public String toString() {
return "[DatasetCreate dataset:" + theDataset.getId() + "]";
}
}