package org.gbif.ipt.service.admin.impl; import org.gbif.doi.service.DoiService; import org.gbif.doi.service.ServiceConfig; import org.gbif.doi.service.datacite.DataCiteService; import org.gbif.doi.service.ezid.EzidService; import org.gbif.ipt.config.AppConfig; import org.gbif.ipt.config.DataDir; import org.gbif.ipt.model.Ipt; import org.gbif.ipt.model.Organisation; import org.gbif.ipt.model.Registration; import org.gbif.ipt.model.Resource; import org.gbif.ipt.model.converter.PasswordConverter; import org.gbif.ipt.model.legacy.LegacyIpt; import org.gbif.ipt.model.legacy.LegacyOrganisation; import org.gbif.ipt.model.legacy.LegacyRegistration; import org.gbif.ipt.model.voc.DOIRegistrationAgency; import org.gbif.ipt.service.AlreadyExistingException; import org.gbif.ipt.service.BaseManager; import org.gbif.ipt.service.DeletionNotAllowedException; import org.gbif.ipt.service.DeletionNotAllowedException.Reason; import org.gbif.ipt.service.InvalidConfigException; import org.gbif.ipt.service.InvalidConfigException.TYPE; import org.gbif.ipt.service.admin.RegistrationManager; import org.gbif.ipt.service.manage.ResourceManager; import org.gbif.ipt.service.registry.RegistryManager; import org.gbif.ipt.utils.FileUtils; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import com.google.common.base.Strings; import com.google.common.collect.Ordering; import com.google.common.io.Closer; import com.google.inject.Inject; import com.google.inject.Singleton; import com.thoughtworks.xstream.XStream; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.log4j.Logger; @Singleton public class RegistrationManagerImpl extends BaseManager implements RegistrationManager { private static final Logger LOG = Logger.getLogger(RegistrationManagerImpl.class); private static final Comparator<Organisation> ORG_BY_NAME_ORD = new Comparator<Organisation>() { public int compare(Organisation left, Organisation right) { return left.getName().compareTo(right.getName()); } }; public static final String PERSISTENCE_FILE_V1 = "registration.xml"; public static final String PERSISTENCE_FILE_V2 = "registration2.xml"; private Registration registration = new Registration(); private final XStream xstreamV1 = new XStream(); private final XStream xstreamV2 = new XStream(); private ResourceManager resourceManager; private RegistryManager registryManager; private DefaultHttpClient client; @Inject public RegistrationManagerImpl(AppConfig cfg, DataDir dataDir, ResourceManager resourceManager, RegistryManager registryManager, PasswordConverter passwordConverter, DefaultHttpClient client) { super(cfg, dataDir); this.resourceManager = resourceManager; defineXstreamMappingV1(); defineXstreamMappingV2(passwordConverter); this.registryManager = registryManager; this.client = client; } public Organisation addAssociatedOrganisation(Organisation organisation) throws AlreadyExistingException, InvalidConfigException { if (organisation != null) { // ensure max 1 DOI account is activated in the IPT if (organisation.isAgencyAccountPrimary() && findPrimaryDoiAgencyAccount() != null && !organisation.getKey() .equals(findPrimaryDoiAgencyAccount().getKey())) { throw new InvalidConfigException(TYPE.REGISTRATION_BAD_CONFIG, "Multiple DOI accounts activated in registration information - only one is allowed."); } log.debug("Adding/updating associated organisation " + organisation.getKey() + " - " + organisation.getName()); registration.getAssociatedOrganisations().put(organisation.getKey().toString(), organisation); } return organisation; } /** * Find the organisation associated to the IPT that has a DOI agency account that has been activated. This * organisation's DOI agency account has been chosen as the only account used to register DOIs for * datasets. * * @return organisation with activated DOI agency account if found, null otherwise */ public Organisation findPrimaryDoiAgencyAccount() { for (Organisation organisation : registration.getAssociatedOrganisations().values()) { if (organisation.isAgencyAccountPrimary()) { return organisation; } } return null; } public DoiService getDoiService() throws InvalidConfigException { Organisation organisation = findPrimaryDoiAgencyAccount(); if (organisation != null) { String username = organisation.getAgencyAccountUsername(); String password = organisation.getAgencyAccountPassword(); DOIRegistrationAgency agency = organisation.getDoiRegistrationAgency(); if (!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password) && agency != null) { ServiceConfig cfg = new ServiceConfig(username, password); if (agency.equals(DOIRegistrationAgency.DATACITE)) { return new DataCiteService(client, cfg); } else if (agency.equals(DOIRegistrationAgency.EZID)) { return new EzidService(client, cfg); } else { throw new InvalidConfigException(TYPE.REGISTRATION_BAD_CONFIG, "DOI agency for " + organisation.getName() + " is not recognized: " + agency.toString()); } } else { throw new InvalidConfigException(TYPE.REGISTRATION_BAD_CONFIG, "DOI agency account for " + organisation.getName() + " is missing information!"); } } else { LOG.debug("No DOI agency account has been added and activated yet to this IPT."); } return null; } public Organisation addHostingOrganisation(Organisation organisation) { if (organisation != null) { log.debug("Adding hosting organisation " + organisation.getKey() + " - " + organisation.getName()); registration.setHostingOrganisation(organisation); } return organisation; } public void addIptInstance(Ipt ipt) { if (ipt != null) { if (ipt.getCreated() == null) { ipt.setCreated(new Date()); } registration.setIpt(ipt); } } /** * Populate ipt instance from LegacyIpt, for all fields. * * @param ipt LegacyOrganisation * * @return Ipt populated from LegacyIpt */ private Ipt createIptFromLegacyIpt(LegacyIpt ipt) { Ipt i = null; if (ipt != null) { i = new Ipt(); String key = (ipt.getKey() == null) ? null : ipt.getKey().toString(); if (key != null) { i.setKey(key); } i.setDescription(Strings.emptyToNull(ipt.getDescription())); i.setWsPassword(Strings.emptyToNull(ipt.getWsPassword())); i.setName(Strings.emptyToNull(ipt.getName())); i.setCreated(ipt.getCreated()); i.setLanguage(Strings.emptyToNull(ipt.getLanguage())); i.setLogoUrl(Strings.emptyToNull(ipt.getLogoUrl())); i.setHomepageURL(Strings.emptyToNull(ipt.getHomepageURL())); i.setOrganisationKey(Strings.emptyToNull(ipt.getOrganisationKey().toString())); i.setPrimaryContactType(Strings.emptyToNull(ipt.getPrimaryContactType())); i.setPrimaryContactPhone(Strings.emptyToNull(ipt.getPrimaryContactPhone())); i.setPrimaryContactLastName(Strings.emptyToNull(ipt.getPrimaryContactLastName())); i.setPrimaryContactFirstName(Strings.emptyToNull(ipt.getPrimaryContactFirstName())); i.setPrimaryContactAddress(Strings.emptyToNull(ipt.getPrimaryContactAddress())); i.setPrimaryContactEmail(Strings.emptyToNull(ipt.getPrimaryContactEmail())); i.setPrimaryContactDescription(Strings.emptyToNull(ipt.getPrimaryContactDescription())); i.setPrimaryContactName(Strings.emptyToNull(ipt.getPrimaryContactName())); } return i; } /** * Populate Organisation instance from LegacyOrganisation for only the key, plus fields not coming from registry. * * @param organisation LegacyOrganisation * * @return Organisation populated from LegacyOrganisation */ private Organisation createOrganisationFromLegacyOrganisation(LegacyOrganisation organisation) { Organisation o = null; if (organisation != null) { o = new Organisation(); String key = (organisation.getKey() == null) ? null : organisation.getKey().toString(); if (key != null) { o.setKey(key); } o.setName(organisation.getName()); o.setAlias(organisation.getAlias()); o.setCanHost(organisation.isCanHost()); o.setPassword(organisation.getPassword()); } return o; } /** * Define XStream used to parse former registration (registration.xml). */ private void defineXstreamMappingV1() { xstreamV1.omitField(LegacyRegistration.class, "associatedOrganisations"); xstreamV1.alias("organisation", LegacyOrganisation.class); xstreamV1.alias("registry", LegacyRegistration.class); } /** * Define XStream used to parse encrypted registration (registration2.xml) with passwords encrypted. * * @param passwordConverter PasswordConverter */ private void defineXstreamMappingV2(PasswordConverter passwordConverter) { xstreamV2.omitField(Registration.class, "associatedOrganisations"); xstreamV2.alias("organisation", Organisation.class); xstreamV2.alias("registry", Registration.class); // encrypt passwords xstreamV2.registerConverter(passwordConverter); } public Organisation delete(String key) throws DeletionNotAllowedException { Organisation org = get(key); if (org != null) { for (Resource resource : resourceManager.list()) { // Ensure the organisation is not associated to any registered resources if (resource.getOrganisation() != null && resource.getOrganisation().equals(org)) { throw new DeletionNotAllowedException(Reason.RESOURCE_REGISTERED_WITH_ORGANISATION, "Resource " + resource.getShortname() + " associated with organisation"); } // Ensure the organisation is not associated to any resources with registered DOIs else if (resource.getDoiOrganisationKey() != null && resource.getDoiOrganisationKey().equals(org.getKey())) { throw new DeletionNotAllowedException(Reason.RESOURCE_DOI_REGISTERED_WITH_ORGANISATION, "Resource " + resource.getShortname() + " has DOI associated with organisation"); } } // Check that the organization is not the hosting organization (IPT is not registered against the organization) Organisation host = registration.getHostingOrganisation(); if (host != null && host.getKey() != null && host.getKey().toString().equals(key)) { throw new DeletionNotAllowedException(Reason.IPT_REGISTERED_WITH_ORGANISATION, "The IPT instance is associated with this organisation"); } registration.getAssociatedOrganisations().remove(key); } return org; } public Organisation get(String key) { if (key == null) { return null; } return registration.getAssociatedOrganisations().get(key); } public Organisation get(UUID key) { if (key == null) { return null; } return registration.getAssociatedOrganisations().get(key.toString()); } public Organisation getHostingOrganisation() { return registration.getHostingOrganisation(); } public Ipt getIpt() { return registration.getIpt(); } public List<Organisation> list() { List<Organisation> organisationList = new ArrayList<Organisation>(); for (Organisation organisation : Ordering.from(ORG_BY_NAME_ORD) .sortedCopy(registration.getAssociatedOrganisations().values())) { if (organisation.isCanHost()) { organisationList.add(organisation); } } return organisationList; } public List<Organisation> listAll() { return Ordering.from(ORG_BY_NAME_ORD).sortedCopy(registration.getAssociatedOrganisations().values()); } public void load() throws InvalidConfigException { Closer closer = Closer.create(); try { Reader registrationReader = FileUtils.getUtf8Reader(dataDir.configFile(PERSISTENCE_FILE_V2)); ObjectInputStream in = closer.register(xstreamV2.createObjectInputStream(registrationReader)); registration.getAssociatedOrganisations().clear(); try { Registration reg = (Registration) in.readObject(); // load the organisation this IPT is registered against addHostingOrganisation(reg.getHostingOrganisation()); addIptInstance(reg.getIpt()); // load the associated organisations while (true) { try { Organisation org = (Organisation) in.readObject(); addAssociatedOrganisation(org); } catch (EOFException e) { // end of file, expected exception! break; } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); } } } catch (EOFException e) { // end of file, expected exception! } catch (AlreadyExistingException e) { log.error(e); } } catch (FileNotFoundException e) { log.warn("Registration information not existing, " + PERSISTENCE_FILE_V2 + " file missing (This is normal when IPT is not registered yet)"); } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); } catch (IOException e) { log.error(e.getMessage(), e); throw new InvalidConfigException(TYPE.REGISTRATION_CONFIG, "Couldnt read the registration information: " + e.getMessage()); } finally { try { closer.close(); } catch (IOException e) { } } // it could be organisations have changed their name or node in the Registry, so update all organisation metadata updateAssociatedOrganisationsMetadata(); } public Organisation getFromDisk(String key) { Closer closer = Closer.create(); SortedMap<String, Organisation> associatedOrganisations = new TreeMap<String, Organisation>(); try { Reader registrationReader = FileUtils.getUtf8Reader(dataDir.configFile(PERSISTENCE_FILE_V2)); ObjectInputStream in = closer.register(xstreamV2.createObjectInputStream(registrationReader)); in.readObject(); // skip over Registration block // now parse the associated organisations while (true) { try { Organisation org = (Organisation) in.readObject(); associatedOrganisations.put(org.getKey().toString(), org); } catch (EOFException e) { // end of file, expected exception! break; } } } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); } catch (IOException e) { log.error(e.getMessage(), e); throw new InvalidConfigException(TYPE.REGISTRATION_CONFIG, "Couldnt read registration info: " + e.getMessage()); } finally { try { closer.close(); } catch (IOException e) { } } // return the organisation requested return associatedOrganisations.get(key); } public void encryptRegistration() throws InvalidConfigException { Closer closer = Closer.create(); File registrationV1 = dataDir.configFile(PERSISTENCE_FILE_V1); if (registrationV1.exists()) { try { Reader registrationReader = FileUtils.getUtf8Reader(registrationV1); ObjectInputStream in = closer.register(xstreamV1.createObjectInputStream(registrationReader)); registration.getAssociatedOrganisations().clear(); try { LegacyRegistration reg = (LegacyRegistration) in.readObject(); // load the organisation this IPT is registered against LegacyOrganisation legacyHostingOrganisation = reg.getHostingOrganisation(); if (legacyHostingOrganisation != null) { Organisation hostingOrganisation = createOrganisationFromLegacyOrganisation(legacyHostingOrganisation); addHostingOrganisation(hostingOrganisation); } // load the IPT installation LegacyIpt legacyIpt = reg.getIpt(); if (legacyIpt != null) { Ipt ipt = createIptFromLegacyIpt(legacyIpt); addIptInstance(ipt); } // load the associated organisations while (true) { try { LegacyOrganisation legacyOrganisation = (LegacyOrganisation) in.readObject(); if (legacyOrganisation != null) { Organisation organisation = createOrganisationFromLegacyOrganisation(legacyOrganisation); addAssociatedOrganisation(organisation); } } catch (EOFException e) { // end of file, expected exception! break; } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); } } } catch (EOFException e) { // end of file, expected exception! } catch (AlreadyExistingException e) { log.error(e); } // ensure changes are persisted to registration2.xml save(); } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); throw new InvalidConfigException(TYPE.REGISTRATION_CONFIG, "Problem reading the registration information: " + e.getMessage()); } catch (IOException e) { log.error(e.getMessage(), e); throw new InvalidConfigException(TYPE.REGISTRATION_CONFIG, "Couldnt read the registration information: " + e.getMessage()); } finally { try { closer.close(); } catch (IOException e) { } // delete former registration configuration (registration.xml) org.apache.commons.io.FileUtils.deleteQuietly(registrationV1); } } } /** * Update the metadata of each organization that has been added to the IPT with the latest version coming from the * Registry. */ private void updateAssociatedOrganisationsMetadata() { try { // 1. update associated organisations' metadata for (Map.Entry<String, Organisation> entry : registration.getAssociatedOrganisations().entrySet()) { Organisation o = entry.getValue(); updateOrganisationMetadata(o); // replace organisation in list of associated organisations now registration.getAssociatedOrganisations().put(entry.getKey(), o); } // 2. update hosting organisation's metadata Organisation hostingOrganisation = registration.getHostingOrganisation(); if (hostingOrganisation != null) { updateOrganisationMetadata(hostingOrganisation); } // ensure changes are persisted to registration2.xml save(); } catch (IOException e) { log.error("A problem occurred saving "); } } /** * For a single organization, update its metadata. Only updates the metadata for an organisation coming from the * registry, not the metadata set by the IPT administrator like can host data, DOI configuration, etc. * * @param organisation Organisation */ private void updateOrganisationMetadata(Organisation organisation) { if (organisation != null) { // the organization key String key = (organisation.getKey() == null) ? null : organisation.getKey().toString(); // retrieve the latest copy of the organisation from the Registry Organisation o = registryManager.getRegisteredOrganisation(key); if (o != null) { String oKey = (o.getKey() == null) ? null : o.getKey().toString(); String oName = Strings.emptyToNull(o.getName()); // sanity check - only the key must be exactly the same, and at least the name must not be null if (oKey != null && oKey.equalsIgnoreCase(key) && oName != null) { // organisation organisation.setName(oName); organisation.setDescription(Strings.emptyToNull(o.getDescription())); organisation.setHomepageURL(Strings.emptyToNull(o.getHomepageURL())); // organisation node organisation.setNodeKey(Strings.emptyToNull(o.getNodeKey())); organisation.setNodeName(Strings.emptyToNull(o.getNodeName())); organisation.setNodeContactEmail(Strings.emptyToNull(o.getNodeContactEmail())); // organisation primary contact organisation.setPrimaryContactName(Strings.emptyToNull(o.getPrimaryContactName())); organisation.setPrimaryContactFirstName(Strings.emptyToNull(o.getPrimaryContactFirstName())); organisation.setPrimaryContactLastName(Strings.emptyToNull(o.getPrimaryContactLastName())); organisation.setPrimaryContactAddress(Strings.emptyToNull(o.getPrimaryContactAddress())); organisation.setPrimaryContactDescription(Strings.emptyToNull(o.getPrimaryContactDescription())); organisation.setPrimaryContactEmail(Strings.emptyToNull(o.getPrimaryContactEmail())); organisation.setPrimaryContactPhone(Strings.emptyToNull(o.getPrimaryContactPhone())); organisation.setPrimaryContactType(Strings.emptyToNull(o.getPrimaryContactType())); log.debug("Organisation (" + key + ") updated with latest metadata from Registry"); } else { log.debug("Update of organisation failed: organisation retrieved from Registry was missing name"); } } else { log.debug("Update of organisation failed: organisation retrieved from Registry was null"); } } else { log.debug("Update of organisation failed: organisation was null"); } } public synchronized void save() throws IOException { log.debug("Saving all user organisations associated to this IPT..."); Writer organisationWriter = FileUtils.startNewUtf8File(dataDir.configFile(PERSISTENCE_FILE_V2)); ObjectOutputStream out = xstreamV2.createObjectOutputStream(organisationWriter, "registration"); out.writeObject(registration); for (Organisation organisation : registration.getAssociatedOrganisations().values()) { out.writeObject(organisation); } out.close(); } }