package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.ControlledVocabAlternate; import edu.harvard.iq.dataverse.ControlledVocabularyValue; import edu.harvard.iq.dataverse.ControlledVocabularyValueServiceBean; import edu.harvard.iq.dataverse.DatasetField; import edu.harvard.iq.dataverse.DatasetFieldConstant; import edu.harvard.iq.dataverse.DatasetFieldType; import edu.harvard.iq.dataverse.DatasetFieldServiceBean; import edu.harvard.iq.dataverse.DatasetFieldType.FieldType; import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.MetadataBlock; import edu.harvard.iq.dataverse.MetadataBlockServiceBean; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.ejb.EJB; import javax.ejb.EJBException; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Response; import org.apache.commons.lang.StringUtils; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.asJsonArray; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import java.util.logging.Level; import java.util.logging.Logger; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.ws.rs.core.Response.Status; @Path("admin/datasetfield") public class DatasetFieldServiceApi extends AbstractApiBean { @EJB DatasetFieldServiceBean datasetFieldService; @EJB DataverseServiceBean dataverseService; @EJB MetadataBlockServiceBean metadataBlockService; @EJB ControlledVocabularyValueServiceBean controlledVocabularyValueService; @GET public Response getAll() { try { List<String> listOfIsHasParentsTrue = new ArrayList<>(); List<String> listOfIsHasParentsFalse = new ArrayList<>(); List<String> listOfIsAllowsMultiplesTrue = new ArrayList<>(); List<String> listOfIsAllowsMultiplesFalse = new ArrayList<>(); for (DatasetFieldType dsf : datasetFieldService.findAllOrderedById()) { if (dsf.isHasParent()) { listOfIsHasParentsTrue.add(dsf.getName()); listOfIsAllowsMultiplesTrue.add(dsf.getName()); } else { listOfIsHasParentsFalse.add(dsf.getName()); listOfIsAllowsMultiplesFalse.add(dsf.getName()); } } final List<DatasetFieldType> requiredFields = datasetFieldService.findAllRequiredFields(); final List<String> requiredFieldNames = new ArrayList<>(requiredFields.size()); for ( DatasetFieldType dt : requiredFields ) { requiredFieldNames.add( dt.getName() ); } return ok( Json.createObjectBuilder().add("haveParents", asJsonArray(listOfIsHasParentsTrue)) .add("noParents", asJsonArray(listOfIsHasParentsFalse)) .add("allowsMultiples", asJsonArray(listOfIsAllowsMultiplesTrue)) .add("allowsMultiples", asJsonArray(listOfIsAllowsMultiplesTrue)) .add("doesNotAllowMultiples", asJsonArray(listOfIsAllowsMultiplesFalse)) .add("required", asJsonArray(requiredFieldNames)) ); } catch (EJBException ex) { Throwable cause = ex; StringBuilder sb = new StringBuilder(); sb.append(ex).append(" "); while (cause.getCause() != null) { cause = cause.getCause(); sb.append(cause.getClass().getCanonicalName()).append(" "); sb.append(cause.getMessage()).append(" "); if (cause instanceof ConstraintViolationException) { ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause; for (ConstraintViolation<?> violation : constraintViolationException.getConstraintViolations()) { sb.append("(invalid value: <<<") .append(violation.getInvalidValue()) .append(">>> for ") .append(violation.getPropertyPath()) .append(" at ") .append(violation.getLeafBean()) .append(" - ") .append(violation.getMessage()) .append(")"); } } } return error(Status.INTERNAL_SERVER_ERROR, sb.toString()); } } @GET @Path("{name}") public Response getByName(@PathParam("name") String name) { try { DatasetFieldType dsf = datasetFieldService.findByName(name); Long id = dsf.getId(); String title = dsf.getTitle(); FieldType fieldType = dsf.getFieldType(); String solrFieldSearchable = dsf.getSolrField().getNameSearchable(); String solrFieldFacetable = dsf.getSolrField().getNameFacetable(); String metadataBlock = dsf.getMetadataBlock().getName(); boolean hasParent = dsf.isHasParent(); boolean allowsMultiples = dsf.isAllowMultiples(); boolean isRequired = dsf.isRequired(); String parentAllowsMultiplesDisplay = "N/A (no parent)"; boolean parentAllowsMultiplesBoolean; if (hasParent) { DatasetFieldType parent = dsf.getParentDatasetFieldType(); parentAllowsMultiplesBoolean = parent.isAllowMultiples(); parentAllowsMultiplesDisplay = Boolean.toString(parentAllowsMultiplesBoolean); } return ok(NullSafeJsonBuilder.jsonObjectBuilder() .add("name", dsf.getName()) .add("id", id ) .add("title", title) .add( "metadataBlock", metadataBlock) .add("fieldType", fieldType.name()) .add("allowsMultiples", allowsMultiples) .add("hasParent", hasParent) .add("parentAllowsMultiples", parentAllowsMultiplesDisplay) .add("solrFieldSearchable", solrFieldSearchable) .add("solrFieldFacetable", solrFieldFacetable) .add("isRequired", isRequired)); } catch ( NoResultException nre ) { return notFound(name); } catch (EJBException | NullPointerException ex) { Throwable cause = ex; StringBuilder sb = new StringBuilder(); sb.append(ex).append(" "); while (cause.getCause() != null) { cause = cause.getCause(); sb.append(cause.getClass().getCanonicalName()).append(" "); sb.append(cause.getMessage()).append(" "); if (cause instanceof ConstraintViolationException) { ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause; for (ConstraintViolation<?> violation : constraintViolationException.getConstraintViolations()) { sb.append("(invalid value: <<<").append(violation.getInvalidValue()).append(">>> for ").append(violation.getPropertyPath()).append(" at ").append(violation.getLeafBean()).append(" - ").append(violation.getMessage()).append(")"); } } } return error( Status.INTERNAL_SERVER_ERROR, sb.toString() ); } } /** * * See also http://irclog.greptilian.com/rest/2015-02-07#i_95635 * * @todo is our convention camelCase? Or lisp-case? Or snake_case? */ @GET @Path("controlledVocabulary/subject") public Response showControlledVocabularyForSubject() { DatasetFieldType subjectDatasetField = datasetFieldService.findByName(DatasetFieldConstant.subject); JsonArrayBuilder possibleSubjects = Json.createArrayBuilder(); for (ControlledVocabularyValue subjectValue : controlledVocabularyValueService.findByDatasetFieldTypeId(subjectDatasetField.getId())) { String subject = subjectValue.getStrValue(); if (subject != null) { possibleSubjects.add(subject); } } return ok(possibleSubjects); } // TODO consider replacing with a @Startup method on the datasetFieldServiceBean @GET @Path("loadNAControlledVocabularyValue") public Response loadNAControlledVocabularyValue() { // the find will throw a javax.persistence.NoResultException if no values are in db // datasetFieldService.findNAControlledVocabularyValue(); TypedQuery<ControlledVocabularyValue> naValueFinder = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabularyValue AS o WHERE o.datasetFieldType is null AND o.strValue = :strvalue", ControlledVocabularyValue.class); naValueFinder.setParameter("strvalue", DatasetField.NA_VALUE); if ( naValueFinder.getResultList().isEmpty() ) { ControlledVocabularyValue naValue = new ControlledVocabularyValue(); naValue.setStrValue(DatasetField.NA_VALUE); datasetFieldService.save(naValue); return ok("NA value created."); } else { return ok("NA value exists."); } } private enum HeaderType { METADATABLOCK, DATASETFIELD, CONTROLLEDVOCABULARY } @POST @Consumes("text/tab-separated-values") @Path("load") public Response loadDatasetFields(File file) { ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "loadDatasetFields"); alr.setInfo( file.getName() ); BufferedReader br = null; String line; String splitBy = "\t"; int lineNumber = 0; HeaderType header = null; JsonArrayBuilder responseArr = Json.createArrayBuilder(); try { br = new BufferedReader(new FileReader("/" + file)); while ((line = br.readLine()) != null) { lineNumber++; String[] values = line.split(splitBy); if (values[0].startsWith("#")) { // Header row switch (values[0]) { case "#metadataBlock": header = HeaderType.METADATABLOCK; break; case "#datasetField": header = HeaderType.DATASETFIELD; break; case "#controlledVocabulary": header = HeaderType.CONTROLLEDVOCABULARY; break; default: throw new IOException("Encountered unknown #header type at line lineNumber " + lineNumber); } } else { switch (header) { case METADATABLOCK: responseArr.add( Json.createObjectBuilder() .add("name", parseMetadataBlock(values)) .add("type", "MetadataBlock")); break; case DATASETFIELD: responseArr.add( Json.createObjectBuilder() .add("name", parseDatasetField(values)) .add("type", "DatasetField") ); break; case CONTROLLEDVOCABULARY: responseArr.add( Json.createObjectBuilder() .add("name", parseControlledVocabulary(values)) .add("type", "Controlled Vocabulary") ); break; default: throw new IOException("No #header defined in file."); } } } } catch (FileNotFoundException e) { alr.setActionResult(ActionLogRecord.Result.BadRequest); alr.setInfo( alr.getInfo() + "// file not found"); return error(Status.EXPECTATION_FAILED, "File not found"); } catch (Exception e) { Logger.getLogger(DatasetFieldServiceApi.class.getName()).log(Level.WARNING, "Error parsing dataset fields:" + e.getMessage(), e); alr.setActionResult(ActionLogRecord.Result.InternalError); alr.setInfo( alr.getInfo() + "// " + e.getMessage()); return error(Status.INTERNAL_SERVER_ERROR, e.getMessage()); } finally { if (br != null) { try { br.close(); } catch (IOException e) { Logger.getLogger(DatasetFieldServiceApi.class.getName()) .log(Level.WARNING, "Error closing the reader while importing Dataset Fields."); } } actionLogSvc.log(alr); } return ok( Json.createObjectBuilder().add("added", responseArr) ); } private String parseMetadataBlock(String[] values) { //Test to see if it exists by name MetadataBlock mdb = metadataBlockService.findByName(values[1]); if (mdb == null){ mdb = new MetadataBlock(); } mdb.setName(values[1]); if (!values[2].isEmpty()){ mdb.setOwner(dataverseService.findByAlias(values[2])); } mdb.setDisplayName(values[3]); metadataBlockService.save(mdb); return mdb.getName(); } private String parseDatasetField(String[] values) { //First see if it exists DatasetFieldType dsf = datasetFieldService.findByName(values[1]); if (dsf == null) { //if not create new dsf = new DatasetFieldType(); } //add(update) values dsf.setName(values[1]); dsf.setTitle(values[2]); dsf.setDescription(values[3]); dsf.setWatermark(values[4]); dsf.setFieldType(FieldType.valueOf(values[5].toUpperCase())); dsf.setDisplayOrder(Integer.parseInt(values[6])); dsf.setDisplayFormat(values[7]); dsf.setAdvancedSearchFieldType(Boolean.parseBoolean(values[8])); dsf.setAllowControlledVocabulary(Boolean.parseBoolean(values[9])); dsf.setAllowMultiples(Boolean.parseBoolean(values[10])); dsf.setFacetable(Boolean.parseBoolean(values[11])); dsf.setDisplayOnCreate(Boolean.parseBoolean(values[12])); dsf.setRequired(Boolean.parseBoolean(values[13])); if (!StringUtils.isEmpty(values[14])) { dsf.setParentDatasetFieldType(datasetFieldService.findByName(values[14])); } dsf.setMetadataBlock(dataverseService.findMDBByName(values[15])); datasetFieldService.save(dsf); return dsf.getName(); } private String parseControlledVocabulary(String[] values) { DatasetFieldType dsv = datasetFieldService.findByName(values[1]); //See if it already exists /* Matching relies on assumption that only one cv value will exist for a given identifier or display value If the lookup queries return multiple matches then retval is null */ //First see if cvv exists based on display name ControlledVocabularyValue cvv = datasetFieldService.findControlledVocabularyValueByDatasetFieldTypeAndStrValue(dsv, values[2], true); //then see if there's a match on identifier ControlledVocabularyValue cvvi = null; if (values[3] != null && !values[3].trim().isEmpty()){ cvvi = datasetFieldService.findControlledVocabularyValueByDatasetFieldTypeAndIdentifier(dsv, values[3]); } //if there's a match on identifier use it if (cvvi != null){ cvv = cvvi; } //if there's no match create a new one if (cvv == null) { cvv = new ControlledVocabularyValue(); cvv.setDatasetFieldType(dsv); //Alt is only for dataload so only add to new for (int i = 5; i < values.length; i++) { ControlledVocabAlternate alt = new ControlledVocabAlternate(); alt.setDatasetFieldType(dsv); alt.setControlledVocabularyValue(cvv); alt.setStrValue(values[i]); cvv.getControlledVocabAlternates().add(alt); } } cvv.setStrValue(values[2]); cvv.setIdentifier(values[3]); cvv.setDisplayOrder(Integer.parseInt(values[4])); datasetFieldService.save(cvv); return cvv.getStrValue(); } }