/* * Created on 13-Feb-2006 * * 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.net.URISyntaxException; import java.util.concurrent.atomic.AtomicLong; import alma.ArchiveIdentifierError.wrappers.AcsJIdentifierUnexpectedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeExhaustedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeLockedEx; import alma.ArchiveIdentifierError.wrappers.AcsJRangeUnlockedEx; import alma.ArchiveIdentifierError.wrappers.AcsJUidAlreadyExistsEx; import alma.archive.range.IdentifierRange; import alma.archive.range.RangeT; import alma.entities.commonentity.EntityRefT; import alma.entities.commonentity.EntityT; /** * This class represents a range of UIDs as created by the Alma archive, * and allows using them as XML document IDs or links to XML documents. * A range is always limited to a finite number of UIDs, given either by an explicitly requested limit, * or otherwise by <code>Long.MAX_VALUE</code> many UIDs. * <p> * This class is implemented as an intelligent wrapper for the castor class {@link IdentifierRange} * which itself gets generated from the schema "IdentifierRange.xsd". * <p> * For a better description, see the <a href="http://almasw.hq.eso.org/almasw/bin/view/Archive/UidLibrary">Archive/UidLibrary wiki page</a>. * <p> * General remark on the length of UIDs: there is no limit on the length of the three "/x012af" parts of a UID, * as was confirmed by awicenec to hsommer on 2007-04-20. It is therefore the Archive's responsibility to * ensure that all code dealing with UIDs (which should all be under archive responsibility anyway) * be updated accordingly whenever the actually generated UIDs exceed the "Long" limit. * * @author simon, hsommer */ public class Range { private final String archiveid; private final boolean isLocked; private final String rangeid; private final AtomicLong documentid; private final long minDocumentid; private final long maxDocumentid; /** * */ public Range(IdentifierRange identifierRange) { archiveid = identifierRange.getArchiveID(); isLocked = identifierRange.getIsLocked(); RangeT r = identifierRange.getRange(); rangeid = r.getRangeID(); String baseid = r.getBaseDocumentID(); minDocumentid = Long.parseLong(baseid,16); documentid = new AtomicLong(minDocumentid); String maxid = r.getMaxDocumentID(); maxDocumentid = ( maxid != null ? Long.parseLong(maxid,16) : Long.MAX_VALUE ); } /** * * @return number of UIDs in the range (no matter how many UIDs have been used so far) */ public long getLength() { return maxDocumentid-minDocumentid+1; } /** * A range can be either locked or unlocked. * This immutable property was originally called "serialised" but was considered too confusing * since this logical concept has nothing to do with technically serializing the castor class to XML. * <p> * TODO: explain better the real meaning of this locking, and the forseen scenarios. * @return true if this object represents a locked range. */ public boolean isLocked() { return isLocked; } /** * Assigns a UID to the <code>EntityT</code> castor object that should be a direct child of an Alma XML entity. * As a means of protection, this call will fail if the entity already has a UID. If you actually need to * replace a UID in some rare cases, please use {@link #replaceUniqueEntityId(EntityT)}. * <p> * The same functionality is offered in {@link ContainerServices#assignUniqueEntityId(EntityT)} which actually delegates to here. */ public void assignUniqueEntityId(EntityT entity) throws AcsJUidAlreadyExistsEx, AcsJRangeLockedEx, AcsJRangeExhaustedEx { setUniqueEntityId(entity, false); } /** * Assigns a UID to the <code>EntityT</code> castor object that should be a direct child of an Alma XML entity. * Unlike {@link #assignUniqueEntityID}, this method will silently replace any existing UID, * which is possibly dangerous. Therefore it should only be used in rare cases where replacing an existing ID is * needed, for example when the ObsPrep tool might translate locally created documents into an archivable format. */ public void replaceUniqueEntityId(EntityT entity) throws AcsJUidAlreadyExistsEx, AcsJRangeLockedEx, AcsJRangeExhaustedEx { setUniqueEntityId(entity, true); } private void setUniqueEntityId(EntityT entity, boolean allowReplacing) throws AcsJUidAlreadyExistsEx, AcsJRangeLockedEx, AcsJRangeExhaustedEx { if (entity == null) { throw new NullPointerException("argument 'entity' must not be null."); } if (!isLocked) { if (entity.getEntityId() != null && entity.getEntityId().length() > 0 && !allowReplacing) { AcsJUidAlreadyExistsEx ex = new AcsJUidAlreadyExistsEx(); ex.setObjectDesc("Entity " + entity.getEntityTypeName()); ex.setUid(entity.getEntityId()); throw ex; } String uid = getNextID(); entity.setEntityId(uid); entity.setEntityIdEncrypted("-- id encryption not yet implemented --"); } else { AcsJRangeLockedEx ex = new AcsJRangeLockedEx(); ex.setRange(rangeIdString()); throw ex; } } /** * Assigns a UID from the range to a given entity <b>reference</b>. * Note that this is different from assigning a UID to an entity. * This method only works if this range is locked, see {@link #isLocked()}. * @param ref */ public void assignUniqueEntityRef(EntityRefT ref) throws AcsJRangeUnlockedEx, AcsJRangeExhaustedEx { if (isLocked) { String uid = getNextID(); ref.setEntityId(uid); } else { AcsJRangeUnlockedEx ex = new AcsJRangeUnlockedEx(); ex.setRange(rangeIdString()); throw ex; } } /** * Returns a UID that is constructed from the archiveID, the rangeID, and the running local document ID. */ private String getNextID() throws AcsJRangeExhaustedEx { long nextID = documentid.getAndIncrement(); if (nextID <= maxDocumentid) { return generateUID(archiveid, rangeid, nextID); } else { documentid.decrementAndGet(); // to avoid a LONG overflow in the zillions of next calls AcsJRangeExhaustedEx ex = new AcsJRangeExhaustedEx(); ex.setRange(rangeIdString()); ex.setRangeMaxDocumentId(""+getMaxDocumentId()); throw ex; } } /** * This method is only exposed for testing purposes, when a UID has to be generated from its constituent parts. * You should normally not use this method. */ public final static String generateUID(String _archiveID, String _rangeID, long _localId) { String uid = "uid://" + _archiveID + "/X" + _rangeID + "/X" + Long.toHexString(_localId); return uid; } /** * Checks whether another UID can be pulled from this range without causing an exception due to an overflow. * <p> * Warning about thread safety: This method does not reserve a UID or anything like that, * thus a returned <code>true</code> value does not necessarily mean that a later call * to one of the assignXYZ etc methods will succeed. * @return true if at least one more UID can be retrieved from this range. */ public boolean hasNextID() { return ( documentid.get() < maxDocumentid ); } long getMaxDocumentId() { return maxDocumentid; } /** * cryptic comment from Simon: TODO: this needs to be moved to protected soon, will require the ArchiveID web service to be moved to this namespace * @return * @throws AcsJIdentifierUnexpectedEx */ public URI next() throws AcsJIdentifierUnexpectedEx, AcsJRangeExhaustedEx { try{ URI uri = new URI(getNextID()); return uri; } catch (URISyntaxException e){ throw new AcsJIdentifierUnexpectedEx(e); } } /** * Gets the UID of this range document itself. * @throws AcsJIdentifierUnexpectedEx if the string from {@link #rangeIdString()} cannot be turned into a URI */ public URI rangeId() throws AcsJIdentifierUnexpectedEx { String uid = rangeIdString(); try { URI uri = new URI(uid); return uri; } catch (URISyntaxException e) { throw new AcsJIdentifierUnexpectedEx(e); } } /** * Gets a String representation of the UID of this range document itself. */ public final String rangeIdString() { String uid = "uid://" + archiveid + "/X" + rangeid + "/X0"; return uid; } }