/* * Created on 20-Sep-2005 * * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration), * All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.container.archive; import java.net.URI; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import alma.ArchiveIdentifierError.wrappers.AcsJIdentifierUnavailableEx; import alma.ArchiveIdentifierError.wrappers.AcsJIdentifierUnexpectedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeExhaustedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeLockedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeUnavailableEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeUnlockedEx; import alma.ArchiveIdentifierError.wrappers.AcsJUidAlreadyExistsEx; import alma.archive.range.IdentifierRange; import alma.entities.commonentity.EntityRefT; import alma.entities.commonentity.EntityT; import alma.xmlstore.IdentifierJ; import alma.xmlstore.IdentifierPackage.NotAvailable; import alma.xmlstore.IdentifierPackage.NotFound; /** * Library-style class that facilitates the use of UID ranges obtained from the identifier archive. * <ul> * <li>The most commonly used feature should be that of assigning a new UID to an XML entity that does not yet have a UID; * the method {@link #assignUniqueEntityId(EntityT, IdentifierJ)} could be used for this, although it is recommended * to instead use the convenience method {@link ContainerServices#assignUniqueEntityId(EntityT)} from the ContainerServices. * Here the caller is not expected to care about to which range of UIDs the new UID belongs, as long as it is a valid and unique ID. * <li>For exotic cases (perhaps ObsPrep) where an existing (e.g. locally created) UID must be replaced by a UID that's valid in the archive, * this class offers the method {@link #replaceUniqueEntityId(EntityT, IdentifierJ)}. * <li>Other more specialized methods allow using specific ranges of UIDs, which is intended for cases where one component allocates * a range and then some other component uses these IDs. As of 2006-09, the only known use case for this is an interaction * between Control and Correlator, which will rely only on the C++ version of this class. * </ul> * Implementation notes: this class has been pulled up from Archive to ACS in order to share the implementation between the ContainerServices * and this library, which otherwise could have remained in the Archive modules. We did not want to split this class, therefore even the methods * that ACS does not need are all included here.<br> * Simon's original implementation has been thoroughly changed: most notably this class is no longer used as a singleton, which would have * caused severe identity and lifecycle problems with logger and identifier archive objects getting shared among independent components. * So far we've kept the original design that hides explicitly requested <code>Range</code> objects by only referring to them through their UIDs * in methods {@link #assignUniqueEntityId(EntityT, URI)}, {@link #assignUniqueEntityRef(EntityRefT, URI)} etc., but it is not clear how useful this is. * @author simon, hsommer */ public class UIDLibrary { private final Logger logger; private static volatile Range defaultRange; private final HashMap<URI, Range> idRanges; private final HashMap<URI, Range> refRanges; /** * Creates the UIDLibrary without making any calls. * @param logger Logger used by this object. */ public UIDLibrary(Logger logger) { this.logger = logger; idRanges = new HashMap<URI, Range>(); refRanges = new HashMap<URI, Range>(); } /** * Assigns a UID from the default range to the <code>EntityT</code> castor class * of an XML-based entity such as a SchedBlock. * <p> * Implementation note: the default range of UIDs is retrieved from the archive at the first call and is then shared among instances * in order to be frugal on UIDs and to minimize archive access. * This means that often the passed in <code>identifier</code> will not be used at all but still must be provided, * because the calling component can not know whether another component or the container has * called this method before. * This method is synchronized to avoid the very unlikely situation that <code>defaultRange.hasNextId</code> succeeds for one thread but then later * assigning the UID still fails because of another thread having stolen the last free UID in the meantime. * * @param identifier the identifier archive from which a new <code>Range</code> can be obtained if necessary. Use <br> * <code>ContainerServices#getTransparentXmlComponent(IdentifierJ.class, identRaw, IdentifierOperations.class);</code> <br> * to create the required XML binding class aware interface from the plain-Corba <code>Identifier</code> object * (<code>identRaw</code>) that is obtained, for example, by <br> * <code>IdentifierHelper.narrow(getDefaultComponent("IDL:alma/xmlstore/Identifier:1.0"))</code>. * @see ContainerServices#assignUniqueEntityId(EntityT) */ public synchronized void assignUniqueEntityId(EntityT entity, IdentifierJ identifier) throws AcsJUidAlreadyExistsEx, AcsJIdentifierUnavailableEx, AcsJRangeUnavailableEx, AcsJIdentifierUnexpectedEx { try { checkDefaultRange(identifier); defaultRange.assignUniqueEntityId(entity); if (logger.isLoggable(Level.FINEST)) { logger.finest("Assigned UID '" + entity.getEntityId() + "' to entity of type " + entity.getEntityTypeName()); } } catch (AcsJUidAlreadyExistsEx e) { throw e; } catch (AcsJRangeUnavailableEx e) { throw e; } catch (AcsJIdentifierUnavailableEx e) { throw e; } catch (Throwable e) { // AcsJRangeLockedEx and AcsJRangeExhaustedEx should not occur for default range, thanks to method checkDefaultRange throw new AcsJIdentifierUnexpectedEx(e); } } /** * Similar to {@link #assignUniqueEntityId(EntityT, IdentifierJ)}, but allows replacing an existing ID. * Only in very special cases such as ObsPrep replacing locally-generated IDs with archive-generated UIDs * should this method be used. Replacing UIDs can easily corrupt the archive because existing links would no longer hold! * @param identifier the identifier archive from which a new <code>Range</code> can be obtained if necessary. Use <br> * <code>ContainerServices#getTransparentXmlComponent(IdentifierJ.class, identRaw, IdentifierOperations.class);</code> <br> * to create the required XML binding class aware interface from the plain-Corba <code>Identifier</code> object * (<code>identRaw</code>) that is obtained, for example, by <br> * <code>IdentifierHelper.narrow(getDefaultComponent("IDL:alma/xmlstore/Identifier:1.0"))</code>. * @see #assignUniqueEntityId(EntityT, IdentifierJ) */ public synchronized void replaceUniqueEntityId(EntityT entity, IdentifierJ identifier) throws AcsJRangeUnavailableEx, AcsJIdentifierUnavailableEx, AcsJIdentifierUnexpectedEx { if (entity == null) { throw new NullPointerException("argument 'entity' must not be null."); } try { String oldUid = entity.getEntityId(); checkDefaultRange(identifier); defaultRange.replaceUniqueEntityId(entity); logger.info("Replaced old UID '" + oldUid + "' with new UID '" + entity.getEntityId() + "' on an entity of type " + entity.getEntityTypeName()); } catch (AcsJRangeUnavailableEx e) { throw e; } catch (AcsJIdentifierUnavailableEx e) { throw e; } catch (Throwable e) { // AcsJRangeLockedEx and AcsJRangeExhaustedEx should not occur for default range, thanks to method checkDefaultRange throw new AcsJIdentifierUnexpectedEx(e); } } /** * Creates a default range on demand, or sets a new default range if the old range has no more UID * (which happens after pulling Long.MAX UIDs, or sooner if a limit was set). * @param identifier the identifier archive from which a new <code>Range</code> can be obtained if necessary. Use <br> * <code>ContainerServices#getTransparentXmlComponent(IdentifierJ.class, identRaw, IdentifierOperations.class);</code> <br> * to create the required XML binding class aware interface from the plain-Corba <code>Identifier</code> object * (<code>identRaw</code>) that is obtained, for example, by <br> * <code>IdentifierHelper.narrow(getDefaultComponent("IDL:alma/xmlstore/Identifier:1.0"))</code>. */ protected synchronized void checkDefaultRange(IdentifierJ identifier) throws AcsJRangeUnavailableEx, AcsJIdentifierUnavailableEx { if (identifier == null) { AcsJIdentifierUnavailableEx ex = new AcsJIdentifierUnavailableEx(); ex.setContextInfo("Provided identifier reference is null."); throw ex; } try { if (defaultRange == null || !defaultRange.hasNextID()) { defaultRange = new Range(identifier.getNewRange()); } } catch (NotAvailable e) { AcsJRangeUnavailableEx ex = new AcsJRangeUnavailableEx(); ex.setRange("default"); throw ex; } } /** * Fetches a new restricted range. * This will return a URI allowing access to the new Range. * The range is automatically stored in the archive. * This method should only be used in very special cases, * see <a href="http://almasw.hq.eso.org/almasw/bin/view/Archive/UidLibrary">Archive/UidLibrary wiki page</a>! * @param identifier the identifier archive from which a new <code>Range</code> can be obtained if necessary. Use <br> * <code>ContainerServices#getTransparentXmlComponent(IdentifierJ.class, identRaw, IdentifierOperations.class);</code> <br> * to create the required XML binding class aware interface from the plain-Corba <code>Identifier</code> object * (<code>identRaw</code>) that is obtained, for example, by <br> * <code>IdentifierHelper.narrow(getDefaultComponent("IDL:alma/xmlstore/Identifier:1.0"))</code>. * @param printLogs Emit logs, iff set to true. * @return the UID of the range, which can be used for example as an argument in {@link #assignUniqueEntityId(EntityT, URI)}. * @throws UniqueIdException if the range cannot be obtained from the archive. */ public URI getNewRestrictedRange(int size, String user, IdentifierJ identifier, boolean printLogs) throws AcsJRangeUnavailableEx, AcsJIdentifierUnexpectedEx { Range range = null; try { if (printLogs) logger.finest("UIDLibrary: Fetching a restricted range"); IdentifierRange idRange = identifier.getNewRestrictedRange(size, user); range = new Range(idRange); } catch (NotAvailable e) { throw new AcsJRangeUnavailableEx(e); } URI uri = range.rangeId(); if (idRanges.containsKey(uri)) { AcsJIdentifierUnexpectedEx ex = new AcsJIdentifierUnexpectedEx(); ex.setContextInfo("Cannot store new range. URI occupied. This should never have happened by design!!"); throw ex; } if (printLogs) logger.finest("UIDLibrary: Storing Range under: "+ uri.toASCIIString()); idRanges.put(uri,range); return uri; } /** * convenience method for the above, printLogs is set to true **/ public URI getNewRestrictedRange(int size, String user, IdentifierJ identifier) throws AcsJRangeUnavailableEx, AcsJIdentifierUnexpectedEx { return getNewRestrictedRange(size, user, identifier, true); } /** * Assigns a uid to the EntityT from the specified range. * This is not permitted with a locked Range object. * This method should only be used in very special cases, * see <a href="http://almasw.hq.eso.org/almasw/bin/view/Archive/UidLibrary">Archive/UidLibrary wiki page</a>! * <p> * TODO: figure out if this is meant to work only if the Range referenced by uri has been loaded previously by this instance. * If so, put a comment that fetchRange must be called first. Otherwise the fetch method could be called automatically. * * @param entity * @param uri the UID of the Range object * @param printLogs set to true, iff logs should be printed */ public void assignUniqueEntityId(EntityT entity, URI uri, boolean printLogs) throws AcsJRangeUnavailableEx, AcsJUidAlreadyExistsEx, AcsJRangeLockedEx, AcsJRangeExhaustedEx { if (idRanges.containsKey(uri)){ if (printLogs && logger.isLoggable(Level.FINEST)) { logger.finest("UIDLibrary: Assigning ID to entity from range " + uri.toASCIIString()); } Range r = idRanges.get(uri); r.assignUniqueEntityId(entity); } else{ AcsJRangeUnavailableEx ex = new AcsJRangeUnavailableEx(); ex.setRange(uri.toASCIIString()); throw ex; } } /** * convenience method for the above, printLogs is set to true **/ public void assignUniqueEntityId(EntityT entity, URI uri) throws AcsJRangeUnavailableEx, AcsJUidAlreadyExistsEx, AcsJRangeLockedEx, AcsJRangeExhaustedEx { assignUniqueEntityId(entity, uri, true); } /** * Fetch an existing range from the archive and deserialise, only certain * operations will be permitted. * This method should only be used in very special cases, * see <a href="http://almasw.hq.eso.org/almasw/bin/view/Archive/UidLibrary">Archive/UidLibrary wiki page</a>! * @param uri * @param identifier the identifier archive from which a new <code>Range</code> can be obtained if necessary. Use <br> * <code>ContainerServices#getTransparentXmlComponent(IdentifierJ.class, identRaw, IdentifierOperations.class);</code> <br> * to create the required XML binding class aware interface from the plain-Corba <code>Identifier</code> object * (<code>identRaw</code>) that is obtained, for example, by <br> * <code>IdentifierHelper.narrow(getDefaultComponent("IDL:alma/xmlstore/Identifier:1.0"))</code>. * * @throws AcsJRangeUnavailableEx */ public void fetchRange(URI uri, String user, IdentifierJ identifier) throws AcsJRangeUnavailableEx { IdentifierRange idRange = null; try{ logger.finest("UIDLibrary: Fetching range: " + uri.toASCIIString()); idRange = identifier.getExistingRange(uri.toASCIIString(),user); } catch (NotFound e) { throw new AcsJRangeUnavailableEx(e); } Range r = new Range(idRange); if (!refRanges.containsKey(uri)){ refRanges.put(uri,r); } else { AcsJRangeUnavailableEx ex = new AcsJRangeUnavailableEx(); ex.setRange(uri.toASCIIString()); throw ex; } } /** * Assigns a UID to an entity reference. * This method should only be used in very special cases, * see <a href="http://almasw.hq.eso.org/almasw/bin/view/Archive/UidLibrary">Archive/UidLibrary wiki page</a>! * Note that this operation is only permitted with a locked range. * @param ref the schema-generated entity reference * @param uri * @throws UniqueIdException */ public void assignUniqueEntityRef(EntityRefT ref, URI uri) throws AcsJRangeUnavailableEx, AcsJRangeExhaustedEx, AcsJRangeUnlockedEx { if (refRanges.containsKey(uri)){ logger.finest("UIDLibrary: Assigning ID Ref to entity from: " + uri.toASCIIString()); Range r = refRanges.get(uri); r.assignUniqueEntityRef(ref); } else{ AcsJRangeUnavailableEx ex = new AcsJRangeUnavailableEx(); ex.setRange(uri.toASCIIString()); throw ex; } } }