/*************************************************************************** * Copyright 2010 Global Biodiversity Information Facility Secretariat * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ***************************************************************************/ package org.gbif.ipt.validation; import org.gbif.api.vocabulary.Language; import org.gbif.ipt.action.BaseAction; import org.gbif.ipt.config.AppConfig; import org.gbif.ipt.model.Resource; import org.gbif.ipt.model.voc.MetadataSection; import org.gbif.ipt.service.admin.RegistrationManager; import org.gbif.ipt.struts2.SimpleTextProvider; import org.gbif.metadata.eml.Address; import org.gbif.metadata.eml.Agent; import org.gbif.metadata.eml.BBox; import org.gbif.metadata.eml.Citation; import org.gbif.metadata.eml.Collection; import org.gbif.metadata.eml.Eml; import org.gbif.metadata.eml.GeospatialCoverage; import org.gbif.metadata.eml.JGTICuratorialUnit; import org.gbif.metadata.eml.JGTICuratorialUnitType; import org.gbif.metadata.eml.KeywordSet; import org.gbif.metadata.eml.MaintenanceUpdateFrequency; import org.gbif.metadata.eml.PhysicalData; import org.gbif.metadata.eml.Point; import org.gbif.metadata.eml.Project; import org.gbif.metadata.eml.StudyAreaDescription; import org.gbif.metadata.eml.TaxonKeyword; import org.gbif.metadata.eml.TaxonomicCoverage; import org.gbif.metadata.eml.TemporalCoverage; import org.gbif.metadata.eml.TemporalCoverageType; import org.gbif.metadata.eml.UserId; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.List; import java.util.regex.Pattern; import javax.annotation.Nullable; import com.google.common.base.Strings; import com.google.inject.Inject; public class EmlValidator extends BaseValidator { protected static Pattern phonePattern = Pattern.compile("[\\w ()/+-\\.]+"); private AppConfig cfg; private RegistrationManager regManager; private SimpleTextProvider simpleTextProvider; @Inject public EmlValidator(AppConfig cfg, RegistrationManager registrationManager, SimpleTextProvider simpleTextProvider) { this.cfg = cfg; this.regManager = registrationManager; this.simpleTextProvider = simpleTextProvider; } /** * Returns a formatted URL string, prefixing it with a default scheme component if its not an absolute URL. * * @return the URL always having a scheme component, or null if incoming URL string was null or empty */ public static String formatURL(String url) { if (!Strings.isNullOrEmpty(url)) { try { URI uri = URI.create(url); if (uri.isAbsolute()) { return url; } else { // adding a default scheme component return "http://" + url; } } catch (IllegalArgumentException e) { return null; } } else { return null; } } /** * Checks if the incoming string representing a URL, is in fact a well-formed URI. * * @return true if the string is a well-formed URI */ public static boolean isWellFormedURI(String url) { if (url != null) { try { new URI(url); return true; } catch (URISyntaxException e) { return false; } } else { return false; } } public static boolean isValidInteger(String integer) { try { Integer.parseInt(integer); return true; } catch (NumberFormatException e) { return false; } } public static boolean isValidPhoneNumber(String phone) { return phone != null && phonePattern.matcher(phone).matches(); } public boolean isValid(Resource resource, @Nullable MetadataSection section) { BaseAction action = new BaseAction(simpleTextProvider, cfg, regManager); validate(action, resource, section); return !(action.hasActionErrors() || action.hasFieldErrors()); } /** * Validate if all metadata sections are valid. For the first section encountered that doesn't validate, an * error message will appear for that section only. * * @param action Action * @param resource resource * @return whether all sections validated or not */ public boolean areAllSectionsValid(BaseAction action, Resource resource) { boolean problemsEncountered = false; for (MetadataSection section : MetadataSection.values()) { validate(action, resource, section); // only highlight first section has errors if ((action.hasActionErrors() || action.hasFieldErrors()) && !problemsEncountered) { action.addActionError(action.getText("manage.failed", new String[] {action.getText("submenu." + section.getName())})); problemsEncountered = true; } } return !problemsEncountered; } /** * Validate an EML document, optionally only a part of it. * </br> * For each section, validation only proceeds if at least one field in the section's form has been entered. * * @param action BaseAction * @param resource resource * @param section EML document section name */ public void validate(BaseAction action, Resource resource, @Nullable MetadataSection section) { if (resource != null) { Eml eml = (resource.getEml() == null) ? new Eml() : resource.getEml(); // set default if (section == null) { section = MetadataSection.BASIC_SECTION; } switch (section) { case BASIC_SECTION: // can't bypass the basic metadata page - it is absolutely mandatory // Title - mandatory if (Strings.isNullOrEmpty(eml.getTitle())) { action.addFieldError("eml.title", action.getText("validation.required", new String[] {action.getText("eml.title")})); } // description - mandatory and greater than 5 chars if (eml.getDescription().isEmpty()) { action .addActionError(action.getText("validation.required", new String[] {action.getText("eml.description")})); } else { // ensure each description is longer than min length int index = 0; for (String d : eml.getDescription()) { if (!exists(d, 5)) { action.addFieldError("eml.description[" + index + "]", action.getText("validation.short", new String[] {action.getText("eml.description"), "5"})); } index++; } } // intellectual rights - mandatory if (Strings.isNullOrEmpty(eml.getIntellectualRights())) { action.addFieldError("eml.intellectualRights.license", action.getText("validation.required", new String[] {action.getText("eml.intellectualRights.license")})); } // publishing organisation - mandatory if (resource.getOrganisation() == null) { action.addFieldError("id", action.getText("validation.required", new String[] {action.getText("portal.home.organisation")})); } else if (regManager.get(resource.getOrganisation().getKey()) == null) { action.addFieldError("id", action.getText("eml.publishingOrganisation.notFound", new String[] {resource.getOrganisation().getKey().toString()})); } // type - mandatory if (Strings.isNullOrEmpty(resource.getCoreType())) { action.addFieldError("resource.coreType", action.getText("validation.required", new String[] {action.getText("resource.coreType")})); } // 3 Mandatory fields with default values set: metadata language, data language, and update frequency // metadata language - mandatory (defaults to 3 letter ISO code for English) if (Strings.isNullOrEmpty(eml.getMetadataLanguage())) { action.addActionWarning(action.getText("eml.metadataLanguage.default")); eml.setMetadataLanguage(Language.ENGLISH.getIso3LetterCode()); } // data language - mandatory unless resource is metadata-only (defaults to English) if (Strings.isNullOrEmpty(eml.getLanguage()) && resource.getCoreType() != null && !resource.getCoreType().equalsIgnoreCase(Resource.CoreRowType.METADATA.toString())) { action.addActionWarning(action.getText("eml.language.default")); eml.setLanguage(Language.ENGLISH.getIso3LetterCode()); } // update frequency - mandatory (defaults to Unknown) if (eml.getUpdateFrequency()==null) { if (resource.getUpdateFrequency() != null) { eml.setUpdateFrequency(resource.getUpdateFrequency().getIdentifier()); action.addActionWarning(action.getText("eml.updateFrequency.default.interval", new String[] {resource.getUpdateFrequency().getIdentifier()})); } else { action.addActionWarning(action.getText("eml.updateFrequency.default")); eml.setUpdateFrequency(MaintenanceUpdateFrequency.UNKOWN.getIdentifier()); } } // Contacts list: at least one field has to have had data entered into it to qualify for validation if (isAgentsListEmpty(eml.getContacts())) { action.addActionError(action.getText("eml.contact.required")); } else { for (int index = 0; index < eml.getContacts().size(); index++) { Agent c = eml.getContacts().get(index); // firstName - optional. But if firstName exists, lastName have to exist if (exists(c.getFirstName()) && !exists(c.getLastName())) { action.addFieldError("eml.contacts[" + index + "].lastName", action.getText("validation.firstname.lastname")); } // directory and personnel id both required (if either is supplied) if (!c.getUserIds().isEmpty()) { if (exists(c.getUserIds().get(0).getDirectory()) && !exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.contacts[" + index + "].userIds[0].identifier", action.getText("validation.personnel")); } else if (!exists(c.getUserIds().get(0).getDirectory()) && exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.contacts[" + index + "].userIds[0].directory", action.getText("validation.directory")); } } // At least one of organisation, position, or a lastName have to exist if (!exists(c.getOrganisation()) && !exists(c.getLastName()) && !exists(c.getPosition())) { action.addActionError(action.getText("validation.lastname.organisation.position")); action.addFieldError("eml.contacts[" + index + "].organisation", action .getText("validation.required", new String[] {action.getText("eml.contact.organisation")})); action.addFieldError("eml.contacts[" + index + "].lastName", action.getText("validation.required", new String[] {action.getText("eml.contact.lastName")})); action.addFieldError("eml.contacts[" + index + "].position", action.getText("validation.required", new String[] {action.getText("eml.contact.position")})); } /* email is optional. But if it exists, should be a valid email address */ if (exists(c.getEmail()) && !isValidEmail(c.getEmail())) { action.addFieldError("eml.contacts[" + index + "].email", action.getText("validation.invalid", new String[] {action.getText("eml.contact.email")})); } /* phone is optional. But if it exists, should match the pattern */ if (exists(c.getPhone()) && !isValidPhoneNumber(c.getPhone())) { action.addFieldError("eml.contacts[" + index + "].phone", action.getText("validation.invalid", new String[] {action.getText("eml.contact.phone")})); } /* Validate the homepage URL from each contact */ if (c.getHomepage() != null) { if (formatURL(c.getHomepage()) == null) { action.addFieldError("eml.contacts[" + index + "].homepage", action.getText("validation.invalid", new String[] {action.getText("eml.contact.homepage")})); } else { c.setHomepage(formatURL(c.getHomepage())); } } } } // Creators list: at least one contact is required, and // at least one field has to have had data entered into it to qualify for validation if (isAgentsListEmpty(eml.getCreators())) { action.addActionError(action.getText("eml.resourceCreator.required")); } else { for (int index = 0; index < eml.getCreators().size(); index++) { Agent c = eml.getCreators().get(index); // firstName - optional. But if firstName exists, lastName have to exist if (exists(c.getFirstName()) && !exists(c.getLastName())) { action.addFieldError("eml.creators[" + index + "].lastName", action.getText("validation.firstname.lastname")); } // directory and personnel id both required (if either is supplied) if (!c.getUserIds().isEmpty()) { if (exists(c.getUserIds().get(0).getDirectory()) && !exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.creators[" + index + "].userIds[0].identifier", action.getText("validation.personnel")); } else if (!exists(c.getUserIds().get(0).getDirectory()) && exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.creators[" + index + "].userIds[0].directory", action.getText("validation.directory")); } } // At least one of organisation, position, or a lastName have to exist if (!exists(c.getOrganisation()) && !exists(c.getLastName()) && !exists(c.getPosition())) { action.addActionError(action.getText("validation.lastname.organisation.position")); action.addFieldError("eml.creators[" + index + "].organisation", action .getText("validation.required", new String[] {action.getText("eml.resourceCreator.organisation")})); action.addFieldError("eml.creators[" + index + "].lastName", action.getText("validation.required", new String[] {action.getText("eml.resourceCreator.lastName")})); action.addFieldError("eml.creators[" + index + "].position", action.getText("validation.required", new String[] {action.getText("eml.resourceCreator.position")})); } /* email is optional. But if it exists, should be a valid email address */ if (exists(c.getEmail()) && !isValidEmail(c.getEmail())) { action.addFieldError("eml.creators[" + index + "].email", action.getText("validation.invalid", new String[] {action.getText("eml.resourceCreator.email")})); } /* phone is optional. But if it exists, should match the pattern */ if (exists(c.getPhone()) && !isValidPhoneNumber(c.getPhone())) { action.addFieldError("eml.creators[" + index + "].phone", action.getText("validation.invalid", new String[] {action.getText("eml.resourceCreator.phone")})); } /* Validate the homepage URL from each contact */ if (c.getHomepage() != null) { if (formatURL(c.getHomepage()) == null) { action.addFieldError("eml.creators[" + index + "].homepage", action.getText("validation.invalid", new String[] {action.getText("eml.resourceCreator.homepage")})); } else { c.setHomepage(formatURL(c.getHomepage())); } } } } // MetadataProviders list: at least one field has to have had data entered into it to qualify for validation if (isAgentsListEmpty(eml.getMetadataProviders())) { action.addActionError(action.getText("eml.metadataProvider.required")); } else { for (int index = 0; index < eml.getMetadataProviders().size(); index++) { Agent c = eml.getMetadataProviders().get(index); // firstName - optional. But if firstName exists, lastName have to exist if (exists(c.getFirstName()) && !exists(c.getLastName())) { action.addFieldError("eml.metadataProviders[" + index + "].lastName", action.getText("validation.firstname.lastname")); } // directory and personnel id both required (if either is supplied) if (!c.getUserIds().isEmpty()) { if (exists(c.getUserIds().get(0).getDirectory()) && !exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.metadataProviders[" + index + "].userIds[0].identifier", action.getText("validation.personnel")); } else if (!exists(c.getUserIds().get(0).getDirectory()) && exists(c.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.metadataProviders[" + index + "].userIds[0].directory", action.getText("validation.directory")); } } // At least one of organisation, position, or a lastName have to exist if (!exists(c.getOrganisation()) && !exists(c.getLastName()) && !exists(c.getPosition())) { action.addActionError(action.getText("validation.lastname.organisation.position")); action.addFieldError("eml.metadataProviders[" + index + "].organisation", action .getText("validation.required", new String[] {action.getText("eml.metadataProvider.organisation")})); action.addFieldError("eml.metadataProviders[" + index + "].lastName", action.getText("validation.required", new String[] {action.getText("eml.metadataProvider.lastName")})); action.addFieldError("eml.metadataProviders[" + index + "].position", action.getText("validation.required", new String[] {action.getText("eml.metadataProvider.position")})); } /* email is optional. But if it exists, should be a valid email address */ if (exists(c.getEmail()) && !isValidEmail(c.getEmail())) { action.addFieldError("eml.metadataProviders[" + index + "].email", action.getText("validation.invalid", new String[] {action.getText("eml.metadataProvider.email")})); } /* phone is optional. But if it exists, should match the pattern */ if (exists(c.getPhone()) && !isValidPhoneNumber(c.getPhone())) { action.addFieldError("eml.metadataProviders[" + index + "].phone", action.getText("validation.invalid", new String[] {action.getText("eml.metadataProvider.phone")})); } /* Validate the homepage URL from each contact */ if (c.getHomepage() != null) { if (formatURL(c.getHomepage()) == null) { action.addFieldError("eml.metadataProviders[" + index + "].homepage", action.getText("validation.invalid", new String[] {action.getText("eml.metadataProvider.homepage")})); } else { c.setHomepage(formatURL(c.getHomepage())); } } } } break; case GEOGRAPHIC_COVERAGE_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isGeoPageEmpty(eml)) { Double coord = 0.0; for (int index = 0; index < eml.getGeospatialCoverages().size(); index++) { // The Bounding coordinates and description are mandatory. if (!eml.getGeospatialCoverages().isEmpty()) { coord = eml.getGeospatialCoverages().get(index).getBoundingCoordinates().getMin().getLongitude(); if (coord == null) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.min.longitude", action .getText("validation.required", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.min.longitude")})); } else if (Double.isNaN(coord)) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.min.longitude", action .getText("validation.invalid", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.min.longitude")})); } coord = eml.getGeospatialCoverages().get(index).getBoundingCoordinates().getMax().getLongitude(); if (coord == null) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.max.longitude", action .getText("validation.required", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.max.longitude")})); } else if (Double.isNaN(coord)) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.max.longitude", action .getText("validation.invalid", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.max.longitude")})); } coord = eml.getGeospatialCoverages().get(index).getBoundingCoordinates().getMax().getLatitude(); if (coord == null) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.max.latitude", action .getText("validation.required", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.max.latitude")})); } else if (Double.isNaN(coord)) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.max.latitude", action .getText("validation.invalid", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.max.latitude")})); } coord = eml.getGeospatialCoverages().get(index).getBoundingCoordinates().getMin().getLatitude(); if (coord == null) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.min.latitude", action .getText("validation.required", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.min.latitude")})); } else if (Double.isNaN(coord)) { action.addFieldError("eml.geospatialCoverages[" + index + "].boundingCoordinates.min.latitude", action .getText("validation.invalid", new String[] {action.getText("eml.geospatialCoverages.boundingCoordinates.min.latitude")})); } /* description - mandatory and greater than 2 chars */ if (Strings.isNullOrEmpty(eml.getGeospatialCoverages().get(index).getDescription())) { action .addFieldError( "eml.geospatialCoverages[" + index + "].description", action .getText("validation.required", new String[] {action.getText("eml.geospatialCoverages.description")})); } else if (!exists(eml.getGeospatialCoverages().get(index).getDescription(), 2)) { action.addFieldError("eml.geospatialCoverages[" + index + "].description", action .getText("validation.short", new String[] {action.getText("eml.geospatialCoverages.description"), "2"})); } } } } else { // If all fields are empty, the <coverage> label in eml.xml will be removed. eml.getGeospatialCoverages().clear(); } break; case TAXANOMIC_COVERAGE_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isTaxonomicPageEmpty(eml)) { // scientific name is required int index = 0; for (TaxonomicCoverage tc : eml.getTaxonomicCoverages()) { int kw = 0; for (TaxonKeyword k : tc.getTaxonKeywords()) { if (!exists(k.getScientificName())) { action.addFieldError("eml.taxonomicCoverages[" + index + "].taxonKeywords[" + kw + "].scientificName", action.getText("validation.required", new String[] {action.getText("eml.taxonomicCoverages.taxonKeyword.scientificName")})); } kw++; } index++; } } break; case TEMPORAL_COVERAGE_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isTemporalPageEmpty(eml)) { int index = 0; for (TemporalCoverage tc : eml.getTemporalCoverages()) { if (tc.getType() == TemporalCoverageType.SINGLE_DATE && !exists(tc.getStartDate())) { action .addFieldError("eml.temporalCoverages[" + index + "].startDate", action.getText("validation.required", new String[] {action.getText("eml.temporalCoverages.startDate")})); } if (tc.getType() == TemporalCoverageType.DATE_RANGE) { if (!exists(tc.getStartDate())) { action.addFieldError("eml.temporalCoverages[" + index + "].startDate", action .getText("validation.required", new String[] {action.getText("eml.temporalCoverages.startDate")})); } if (!exists(tc.getEndDate())) { action .addFieldError("eml.temporalCoverages[" + index + "].endDate", action.getText("validation.required", new String[] {action.getText("eml.temporalCoverages.endDate")})); } } if (tc.getType() == TemporalCoverageType.FORMATION_PERIOD && !exists(tc.getFormationPeriod())) { action .addFieldError( "eml.temporalCoverages[" + index + "].formationPeriod", action .getText("validation.required", new String[] {action.getText("eml.temporalCoverages.formationPeriod")})); } if (tc.getType() == TemporalCoverageType.LIVING_TIME_PERIOD && !exists(tc.getLivingTimePeriod())) { action.addFieldError("eml.temporalCoverages[" + index + "].livingTimePeriod", action .getText("validation.required", new String[] {action.getText("eml.temporalCoverages.livingTimePeriod")})); } index++; } } break; case KEYWORDS_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isKeywordsPageEmpty(eml)) { int index = 0; for (KeywordSet ks : eml.getKeywords()) { // TODO: remove check for "null" after fixing problem in gbif-metadata-profile if (!exists(ks.getKeywordsString()) || ks.getKeywordsString().equalsIgnoreCase("null")) { action.addFieldError("eml.keywords[" + index + "].keywordsString", action.getText("validation.required", new String[] {action.getText("eml.keywords.keywordsString")})); } if (!exists(ks.getKeywordThesaurus())) { action.addFieldError("eml.keywords[" + index + "].keywordThesaurus", action.getText("validation.required", new String[] {action.getText("eml.keywords.keywordThesaurus")})); } index++; } } break; case PARTIES_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isAgentsListEmpty(eml.getAssociatedParties())) { for (int index = 0; index < eml.getAssociatedParties().size(); index++) { Agent ap = eml.getAssociatedParties().get(index); // firstName - optional. But if firstName exists, lastName have to exist if (exists(ap.getFirstName()) && !exists(ap.getLastName())) { action.addFieldError("eml.associatedParties[" + index + "].lastName", action.getText("validation.firstname.lastname")); } // directory and personnel id both required (if either is supplied) if (!ap.getUserIds().isEmpty()) { if (exists(ap.getUserIds().get(0).getDirectory()) && !exists(ap.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.associatedParties[" + index + "].userIds[0].identifier", action.getText("validation.personnel")); } else if (!exists(ap.getUserIds().get(0).getDirectory()) && exists(ap.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.associatedParties[" + index + "].userIds[0].directory", action.getText("validation.directory")); } } // At least one of organisation, position, or a lastName have to exist if (!exists(ap.getOrganisation()) && !exists(ap.getLastName()) && !exists(ap.getPosition())) { action.addActionError(action.getText("validation.lastname.organisation.position")); action.addFieldError("eml.associatedParties[" + index + "].organisation", action .getText("validation.required", new String[] {action.getText("eml.associatedParties.organisation")})); action.addFieldError("eml.associatedParties[" + index + "].lastName", action.getText("validation.required", new String[] {action.getText("eml.associatedParties.lastName")})); action.addFieldError("eml.associatedParties[" + index + "].position", action.getText("validation.required", new String[] {action.getText("eml.associatedParties.position")})); } /* email is optional. But if it exists, should be a valid email address */ if (exists(ap.getEmail()) && !isValidEmail(ap.getEmail())) { action.addFieldError("eml.associatedParties[" + index + "].email", action.getText("validation.invalid", new String[] {action.getText("eml.associatedParties.email")})); } /* phone is optional. But if it exists, should match the pattern */ if (exists(ap.getPhone()) && !isValidPhoneNumber(ap.getPhone())) { action.addFieldError("eml.associatedParties[" + index + "].phone", action.getText("validation.invalid", new String[] {action.getText("eml.associatedParties.phone")})); } /* Validate the homepage URL from each associated parties */ if (ap.getHomepage() != null) { if (formatURL(ap.getHomepage()) == null) { action.addFieldError("eml.associatedParties[" + index + "].homepage", action.getText("validation.invalid", new String[] {action.getText("eml.associatedParties.homepage")})); } else { ap.setHomepage(formatURL(ap.getHomepage())); } } } } break; case PROJECT_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isProjectPageEmpty(eml)) { // title is required if (!exists(eml.getProject().getTitle()) || eml.getProject().getTitle().trim().length() == 0) { action.addFieldError("eml.project.title", action.getText("validation.required", new String[] {action.getText("eml.project.title")})); } // Personnel list: at least one field has to have had data entered into it to qualify for validation if (isAgentsListEmpty(eml.getProject().getPersonnel())) { action.addActionError(action.getText("eml.project.personnel.required")); } else { for (int index = 0; index < eml.getProject().getPersonnel().size(); index++) { Agent p = eml.getProject().getPersonnel().get(index); // firstName - optional. But if firstName exists, lastName have to exist if (exists(p.getFirstName()) && !exists(p.getLastName())) { action.addFieldError("eml.project.personnel[" + index + "].lastName", action.getText("validation.firstname.lastname")); } // At least a lastName has to exist else if (!exists(p.getLastName())) { action.addFieldError("eml.project.personnel[" + index + "].lastName", action.getText("validation.required", new String[] {action.getText("eml.project.personnel.lastName")})); } // directory and personnel id both required (if either is supplied) if (!p.getUserIds().isEmpty()) { if (exists(p.getUserIds().get(0).getDirectory()) && !exists(p.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.project.personnel[" + index + "].userIds[0].identifier", action.getText("validation.personnel")); } else if (!exists(p.getUserIds().get(0).getDirectory()) && exists(p.getUserIds().get(0).getIdentifier())) { action.addFieldError("eml.project.personnel[" + index + "].userIds[0].directory", action.getText("validation.directory")); } } } } } break; case METHODS_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isMethodsPageEmpty(eml)) { boolean emptyFields = false; if (Strings.isNullOrEmpty(eml.getSampleDescription()) && Strings.isNullOrEmpty(eml.getStudyExtent()) && Strings.isNullOrEmpty(eml.getQualityControl())) { eml.setSampleDescription(null); eml.setStudyExtent(null); eml.setQualityControl(null); emptyFields = true; } // method step required int index = 0; for (String method : eml.getMethodSteps()) { if (method.trim().length() == 0) { if (emptyFields && index == 0) { eml.getMethodSteps().clear(); break; } else { action.addFieldError("eml.methodSteps[" + index + "]", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } } index++; } // both study extent and sampling description required if either one is present if (!emptyFields) { if (!Strings.isNullOrEmpty(eml.getSampleDescription()) && Strings.isNullOrEmpty(eml.getStudyExtent())) { action.addFieldError("eml.studyExtent", action.getText("validation.required", new String[] {action.getText("eml.studyExtent")})); } if (!Strings.isNullOrEmpty(eml.getStudyExtent()) && Strings.isNullOrEmpty(eml.getSampleDescription())) { action.addFieldError("eml.sampleDescription", action.getText("validation.required", new String[] {action.getText("eml.sampleDescription")})); } } } break; case CITATIONS_SECTION: // at least one field has to have had data entered into it to qualify for validation, unless auto-generation // of citation is turned on if (!isCitationsPageEmpty(eml) || resource.isCitationAutoGenerated()) { // recreate the auto-generated citation if auto-generation is turned on if (resource.isCitationAutoGenerated()) { // resource homepage is potential citation identifier URI homepage = cfg.getResourceVersionUri(resource.getShortname(), resource.getNextVersion()); String citation = resource.generateResourceCitation(resource.getNextVersion(), homepage); eml.getCitation().setCitation(citation); } // evaluate Citation if (eml.getCitation() != null) { // citation identifier must be between 2 and 100 characters long if (!Strings.isNullOrEmpty(eml.getCitation().getIdentifier()) && !existsInRange(eml.getCitation().getIdentifier(), 2, 100)) { action.addFieldError("eml.citation.identifier", action.getText("validation.field.invalidSize", new String[] {action.getText("eml.citation.identifier"), "2", "100"})); } // citation text is required, while identifier attribute is optional if (exists(eml.getCitation().getIdentifier()) && !exists(eml.getCitation().getCitation())) { action.addFieldError("eml.citation.citation", action.getText("validation.required", new String[] {action.getText("eml.citation.citation")})); } } int index = 0; for (Citation citation : eml.getBibliographicCitations()) { if (!Strings.isNullOrEmpty(citation.getIdentifier()) && !exists(citation.getIdentifier())) { action.addFieldError("eml.bibliographicCitationSet.bibliographicCitations[" + index + "].identifier", action .getText("validation.field.blank", new String[] {action.getText("eml.bibliographicCitationSet.bibliographicCitations.identifier")})); } if (!exists(citation.getCitation())) { action.addFieldError("eml.bibliographicCitationSet.bibliographicCitations[" + index + "].citation", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } index++; } } break; case COLLECTIONS_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isCollectionsPageEmpty(eml)) { // at least one collection with name filled in is required if (eml.getCollections().isEmpty()) { action.addActionError(action.getText("eml.collection.required")); } for (int index = 0; index < eml.getCollections().size(); index++) { Collection c = eml.getCollections().get(index); // collection name is required, collection id and parent collection id are NOT required if (!exists(c.getCollectionName())) { action.addFieldError("eml.collections[" + index + "].collectionName", action.getText("validation.required", new String[] {action.getText("eml.collectionName")})); } } // TODO ensure this validation works with drop down, and bypasses default for (int index = 0; index < eml.getSpecimenPreservationMethods().size(); index++) { String preservationMethod = eml.getSpecimenPreservationMethods().get(index); // collection name is required, collection id and parent collection id are NOT required if (Strings.isNullOrEmpty(preservationMethod)) { action.addFieldError("eml.specimenPreservationMethods[" + index + "]", action.getText("validation.required", new String[] {action.getText("eml.specimenPreservationMethod")})); } } int index = 0; for (JGTICuratorialUnit jcu : eml.getJgtiCuratorialUnits()) { if (jcu.getType() == JGTICuratorialUnitType.COUNT_RANGE) { if (!exists(jcu.getRangeStart())) { action.addFieldError("eml.jgtiCuratorialUnits[" + index + "].rangeStart", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } if (!exists(jcu.getRangeEnd())) { action.addFieldError("eml.jgtiCuratorialUnits[" + index + "].rangeEnd", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } } if (jcu.getType() == JGTICuratorialUnitType.COUNT_WITH_UNCERTAINTY) { if (!exists(jcu.getRangeMean())) { action.addFieldError("eml.jgtiCuratorialUnits[" + index + "].rangeMean", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } if (!exists(jcu.getUncertaintyMeasure())) { action.addFieldError("eml.jgtiCuratorialUnits[" + index + "].uncertaintyMeasure", action.getText("validation.required", new String[] {action.getText("validation.field.required")})); } } if (!exists(jcu.getUnitType())) { action .addFieldError( "eml.jgtiCuratorialUnits[" + index + "].unitType", action.getText("validation.required", new String[] {action.getText("eml.jgtiCuratorialUnits.unitType")})); } index++; } } break; case PHYSICAL_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isPhysicalPageEmpty(eml)) { // null or empty URLs bypass validation if (!Strings.isNullOrEmpty(eml.getDistributionUrl())) { // retrieve a formatted homepage URL including scheme component String formattedUrl = formatURL(eml.getDistributionUrl()); if (formattedUrl == null || !isWellFormedURI(formattedUrl)) { action.addFieldError("eml.distributionUrl", action.getText("validation.invalid", new String[] {action.getText("eml.distributionUrl")})); } else { eml.setDistributionUrl(formattedUrl); } } // character set, download URL, and data format are required int index = 0; for (PhysicalData pd : eml.getPhysicalData()) { // name required if (!exists(pd.getName())) { action.addFieldError("eml.physicalData[" + index + "].name", action.getText("validation.required", new String[] {action.getText("eml.physicalData.name")})); } // character set required if (!exists(pd.getCharset())) { action.addFieldError("eml.physicalData[" + index + "].charset", action.getText("validation.required", new String[] {action.getText("eml.physicalData.charset")})); } // download URL required if (!exists(pd.getDistributionUrl())) { action.addFieldError("eml.physicalData[" + index + "].distributionUrl", action .getText("validation.required", new String[] {action.getText("eml.physicalData.distributionUrl")})); } // data format required if (!exists(pd.getFormat())) { action.addFieldError("eml.physicalData[" + index + "].format", action.getText("validation.required", new String[] {action.getText("eml.physicalData.format")})); } // data format version is optional - so skip /* Validate distribution URL form each Physical data */ String formattedDistributionUrl = formatURL(pd.getDistributionUrl()); if (formattedDistributionUrl == null || !isWellFormedURI(formattedDistributionUrl)) { action.addFieldError("eml.physicalData[" + index + "].distributionUrl", action .getText("validation.invalid", new String[] {action.getText("eml.physicalData.distributionUrl")})); } else { pd.setDistributionUrl(formattedDistributionUrl); } index++; } } break; case ADDITIONAL_SECTION: // at least one field has to have had data entered into it to qualify for validation if (!isAdditionalPageEmpty(eml)) { // ensure each alternative id is longer than min length int index = 0; for (String ai : eml.getAlternateIdentifiers()) { if (!exists(ai)) { action.addFieldError("eml.alternateIdentifiers[" + index + "]", action.getText("validation.required", new String[] {action.getText("eml.alternateIdentifier")})); } index++; } } break; default: break; } } } /** * Determine if the Project page is empty. In other words, the user hasn't entered any information for a single field * yet. There is a total of 7 fields on this page. * * @param eml EML * @return whether the Project page is empty or not. */ private boolean isProjectPageEmpty(Eml eml) { if (eml.getProject() != null) { Project project = eml.getProject(); List<Agent> personnelList = project.getPersonnel(); StudyAreaDescription area = project.getStudyAreaDescription(); // total of 4 fields on page, plus multiple personnel composed of first name, last name, and role String title = project.getTitle(); String funding = project.getFunding(); String design = project.getDesignDescription(); String desc = area.getDescriptorValue(); return (Strings.isNullOrEmpty(title) && Strings.isNullOrEmpty(funding) && Strings.isNullOrEmpty(design) && Strings.isNullOrEmpty(desc) && isAgentsListEmpty(personnelList)); } return false; } /** * Determine if the Methods page is empty. In other words, the user hasn't entered any information for a single field * yet. There is a total of 4 fields on this page. The step description can be multiple. * * @param eml EML * @return whether the Methods page is empty or not. */ private boolean isMethodsPageEmpty(Eml eml) { // total of 4 fields on page String studyExtent = eml.getStudyExtent(); String sample = eml.getSampleDescription(); String quality = eml.getQualityControl(); List<String> methods = eml.getMethodSteps(); // there must be absolutely nothing entered for any method steps for (String method : methods) { if (!Strings.isNullOrEmpty(method)) { return false; } } return (Strings.isNullOrEmpty(studyExtent) && Strings.isNullOrEmpty(sample) && Strings.isNullOrEmpty(quality)); } /** * Determine if the Citations page is empty. In other words, the user hasn't entered any information for a single * field yet. There is a total of 4 fields on this page. The bibliographic citation can be multiple. * * @param eml EML * @return whether the Citations page is empty or not. */ private boolean isCitationsPageEmpty(Eml eml) { // is the Citation empty? boolean citationIsEmpty = isCitationEmpty(eml.getCitation()); if (!citationIsEmpty) { return false; } // are all the bibliographic citations empty? for (Citation bibCitation : eml.getBibliographicCitations()) { boolean isBibCitationEmpty = isCitationEmpty(bibCitation); if (!isBibCitationEmpty) { return false; } } // otherwise return true; } /** * Determine if a Citation is empty. In other words, the user hasn't entered any information for a single * field yet. There is a total of 2 fields. * * @param citation citation * @return whether the Citation is empty or not. */ private boolean isCitationEmpty(Citation citation) { if (citation != null) { String citationId = citation.getIdentifier(); String citationText = citation.getCitation(); return (Strings.isNullOrEmpty(citationId) && Strings.isNullOrEmpty(citationText)); } return true; } /** * Determine if the Collections page is empty. In other words, the user hasn't entered any information for a single * field yet. The curatorial section and collection section can be multiple. * * @param eml EML * @return whether the Collections page is empty or not. */ private boolean isCollectionsPageEmpty(Eml eml) { // check whether all specimen preservation methods are empty for (String preservationMethod: eml.getSpecimenPreservationMethods()) { if (!Strings.isNullOrEmpty(preservationMethod)) { return false; } } // check whether all collection are empty for (Collection collection: eml.getCollections()) { boolean isCollectionEmpty = isCollectionEmpty(collection); if (!isCollectionEmpty) { return false; } } // check whether all curatorial units are empty for (JGTICuratorialUnit unit : eml.getJgtiCuratorialUnits()) { boolean isUnitEmpty = isJGTICuratorialUnitEmpty(unit); if (!isUnitEmpty) { return false; } } return true; } /** * Determine if a JGTICuratorialUnit is empty. In other words, the user hasn't entered any information for a * single field yet. * * @param unit JGTICuratorialUnit * @return whether the JGTICuratorialUnit page is empty or not. */ private boolean isJGTICuratorialUnitEmpty(JGTICuratorialUnit unit) { if (unit != null) { String unitType = unit.getUnitType(); int rangeEnd = (unit.getRangeEnd() == null) ? 0 : unit.getRangeEnd(); int rangeStart = (unit.getRangeStart() == null) ? 0 : unit.getRangeStart(); int uncertainty = (unit.getUncertaintyMeasure() == null) ? 0 : unit.getUncertaintyMeasure(); int mean = (unit.getRangeMean() == null) ? 0 : unit.getRangeMean(); return (Strings.isNullOrEmpty(unitType) && rangeEnd == 0 && rangeStart == 0 && uncertainty == 0 && mean == 0); } return true; } /** * Determine if a Collection is empty. In other words, the user hasn't entered any information for a * single field yet. * * @param collection collection * @return whether the Collection section empty or not. */ private boolean isCollectionEmpty(Collection collection) { if (collection != null) { String collectionName = collection.getCollectionName(); String collectionId = collection.getCollectionId(); String parentCollectionId = collection.getParentCollectionId(); return (Strings.isNullOrEmpty(collectionName) && Strings.isNullOrEmpty(collectionId) && Strings.isNullOrEmpty(parentCollectionId)); } return true; } /** * Determine if the Physical page is empty. In other words, the user hasn't entered any information for a single * field yet. There is a total of 6 fields on this page. The link section can be multiple. * * @param eml EML * @return whether the Physical page is empty or not. */ private boolean isPhysicalPageEmpty(Eml eml) { // total of 8 fields on page String homepageUrl = eml.getDistributionUrl(); // check all external links for (PhysicalData data : eml.getPhysicalData()) { boolean isLinkEmpty = isExternalLinkEmpty(data); if (!isLinkEmpty) { return false; } } // otherwise it all comes down to the homepage URL return Strings.isNullOrEmpty(homepageUrl); } /** * Determine if a PhysicalData is empty. In other words, the user hasn't entered any information for a single * field yet. * * @param data PhysicalData * @return whether the PhysicalData is empty or not. */ private boolean isExternalLinkEmpty(PhysicalData data) { if (data != null) { String charset = data.getCharset(); String format = data.getFormat(); String formatVersion = data.getFormatVersion(); String distributionUrl = data.getDistributionUrl(); String name = data.getName(); return (Strings.isNullOrEmpty(charset) && Strings.isNullOrEmpty(format) && Strings.isNullOrEmpty(formatVersion) && Strings.isNullOrEmpty(distributionUrl) && Strings.isNullOrEmpty(name)); } return true; } /** * Determine if the Keywords page is empty. In other words, the user hasn't entered any information for a single * field yet. There is a total of 2 fields on this page. The 2 fields together can be multiple. * * @param eml EML * @return whether the Keywords page is empty or not. */ private boolean isKeywordsPageEmpty(Eml eml) { // total of 2 fields on page if (!eml.getKeywords().isEmpty()) { KeywordSet set1 = eml.getKeywords().get(0); return Strings.isNullOrEmpty(set1.getKeywordsString()) && Strings.isNullOrEmpty(set1.getKeywordThesaurus()); } return true; } /** * Determine if the Additional page is empty. In other words, the user hasn't entered any information for a single * field yet. The alternate identifier can be multiple. * * @param eml EML * @return whether the Additional page is empty or not. */ private boolean isAdditionalPageEmpty(Eml eml) { // total of 5 editable fields on page, including repeating alt. id String logo = eml.getLogoUrl(); String rights = eml.getRights(); String info = eml.getAdditionalInfo(); String purpose = eml.getPurpose(); // skip pubDate - it's auto-set // skip hierarchy - it's auto-set for (String id : eml.getAlternateIdentifiers()) { if (!Strings.isNullOrEmpty(id)) { return false; } } return (Strings.isNullOrEmpty(logo) && Strings.isNullOrEmpty(rights) && Strings.isNullOrEmpty(info) && Strings.isNullOrEmpty(purpose)); } /** * Determine if the Temporal page is empty. In other words, the user hasn't entered any information for a single * field yet. The temporal coverages can be multiple. * * @param eml EML * @return whether the Temporal page is empty or not. */ private boolean isTemporalPageEmpty(Eml eml) { // total of 1 editable repeatable section on page List<TemporalCoverage> coverages = eml.getTemporalCoverages(); // iterate through them, they all must be empty, otherwise they all get validated for (TemporalCoverage coverage : coverages) { boolean isEmtpy = isTemporalCoverageEmpty(coverage); // have we found a non-empty coverage? if (!isEmtpy) { return false; } } // otherwise, at least one coverage was not empty, and they must all be validated return true; } /** * Determine if a single TemporalCoverage is empty. In other words, the user hasn't entered any information for a * single field yet. * * @param cov TemporalCoverage * @return whether the TemporalCoverage is empty or not. */ private boolean isTemporalCoverageEmpty(TemporalCoverage cov) { if (cov != null) { String formationPeriod = cov.getFormationPeriod(); Date end = cov.getEndDate(); String period = cov.getLivingTimePeriod(); Date start = cov.getStartDate(); return (Strings.isNullOrEmpty(formationPeriod) && end == null && Strings.isNullOrEmpty(period) && start == null); } return true; } /** * Determine if the Taxonomic page is empty. In other words, the user hasn't entered any information for a single * field yet. The taxonomic coverages can be multiple. * * @param eml EML * @return whether the Taxonomic page is empty or not. */ private boolean isTaxonomicPageEmpty(Eml eml) { // total of 1 editable repeatable section on page for (TaxonomicCoverage cov : eml.getTaxonomicCoverages()) { boolean isTaxonomicCoverageEmpty = isTaxonomicCoverageEmpty(cov); if (!isTaxonomicCoverageEmpty) { return false; } } return true; } /** * Determine if a TaxonomicCoverage is empty. In other words, the user hasn't entered any information for a single * field yet. * * @param cov TaxonomicCoverage * @return whether the TaxonomicCoverage is empty or not. */ private boolean isTaxonomicCoverageEmpty(TaxonomicCoverage cov) { if (cov != null) { String description = cov.getDescription(); // check all TaxonKeyword are empty for (TaxonKeyword word : cov.getTaxonKeywords()) { boolean isTaxonKeywordEmpty = isTaxonKeywordEmpty(word); if (!isTaxonKeywordEmpty) { return false; } } // gotten here means all TaxonKeyword were empty, therefore the only thing left to check is the description return Strings.isNullOrEmpty(description); } return true; } /** * Determine if a TaxonKeyword is empty. In other words, the user hasn't entered any information for a single * field yet. * * @param word TaxonKeyword * @return whether the TaxonKeyword is empty or not. */ private boolean isTaxonKeywordEmpty(TaxonKeyword word) { if (word != null) { String scientificName = word.getScientificName(); String common = word.getCommonName(); String rank = word.getRank(); return Strings.isNullOrEmpty(scientificName) && Strings.isNullOrEmpty(common) && Strings.isNullOrEmpty(rank); } return true; } /** * Determine if the Geo page is empty. In other words, the user hasn't entered any information for a single * field yet. The geo coverages can be multiple. * * @param eml EML * @return whether the Geo page is empty or not. */ private boolean isGeoPageEmpty(Eml eml) { if (!eml.getGeospatialCoverages().isEmpty()) { GeospatialCoverage cov1 = eml.getGeospatialCoverages().get(0); String description = cov1.getDescription(); BBox bbox = cov1.getBoundingCoordinates(); Point p1 = bbox.getMin(); Point p2 = bbox.getMax(); if (p1 != null && p2 != null) { Double lat1 = p1.getLatitude(); Double lon1 = p1.getLongitude(); Double lat2 = p2.getLatitude(); Double lon2 = p2.getLongitude(); return (lat1 == null && lon1 == null && lat2 == null && lon2 == null && Strings.isNullOrEmpty(description)); } else { return Strings.isNullOrEmpty(description); } } return true; } /** * Determine if the Agent is empty. In other words, the user hasn't entered any information for a single * field yet. * * @param agent Agent * * @return whether the Agent is empty or not. */ private boolean isAgentEmpty(Agent agent) { if (agent != null) { String first = agent.getFirstName(); String last = agent.getLastName(); String email = agent.getEmail(); String home = agent.getHomepage(); String org = agent.getOrganisation(); String phone = agent.getPhone(); String position = agent.getPosition(); String city = null; String street = null; String country = null; String code = null; String province = null; Address address = agent.getAddress(); if (address != null) { city = address.getCity(); street = address.getAddress(); country = address.getCountry(); code = address.getPostalCode(); province = address.getProvince(); } // only one userId supported String directory = null; String identifier = null; List<UserId> userIds = agent.getUserIds(); if (!userIds.isEmpty()) { UserId userId = userIds.get(0); directory = userId.getDirectory(); identifier = userId.getIdentifier(); } return (Strings.isNullOrEmpty(city) && Strings.isNullOrEmpty(street) && Strings.isNullOrEmpty(country) && Strings.isNullOrEmpty(code) && Strings.isNullOrEmpty(province) && Strings.isNullOrEmpty(first) && Strings.isNullOrEmpty(last) && Strings.isNullOrEmpty(email) && Strings.isNullOrEmpty(home) && Strings.isNullOrEmpty(org) && Strings.isNullOrEmpty(phone) && Strings.isNullOrEmpty(position) && Strings.isNullOrEmpty(directory) && Strings.isNullOrEmpty(identifier)); } return true; } /** * Determine if the Agent list is empty (there isn't a single non-empty agent). In other words, the user hasn't * entered any information for a single field yet. * * @param agents list * @return false if a non-empty agent was encountered, or true otherwise */ private boolean isAgentsListEmpty(List<Agent> agents) { for (Agent agent : agents) { boolean isEmpty = isAgentEmpty(agent); // we are interested in finding a non-empty agent if (!isEmpty) { return false; } } return true; } }