001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.util;
029
030import org.opencms.main.CmsIllegalArgumentException;
031import org.opencms.main.CmsInitException;
032import org.opencms.main.CmsLog;
033
034import java.io.Externalizable;
035import java.io.IOException;
036import java.io.ObjectInput;
037import java.io.ObjectOutput;
038
039import org.apache.commons.logging.Log;
040
041import org.safehaus.uuid.EthernetAddress;
042import org.safehaus.uuid.UUID;
043import org.safehaus.uuid.UUIDGenerator;
044
045/**
046 * Generates a UUID using spatial and temporal uniqueness.<p>
047 *
048 * Spatial uniqueness is derived from
049 * ethernet address (MAC, 802.1); temporal from system clock.<p>
050 *
051 * For more information about the algorithm used, please see
052 * <a href="http://www.opengroup.org/dce/info/draft-leach-uuids-guids-01.txt">
053 * draft-leach-uuids-guids-01.txt</a>.<p>
054 *
055 * Because Java is unable to read the MAC address of the machine
056 * (without using JNI), the MAC address has to be provided first
057 * by using the static {@link #init(String)} method.<p>
058 *
059 * This class is just a facade wrapper for the "real" UUID implementation.<p>
060 *
061 * @since 6.0.0
062 */
063public final class CmsUUID extends Object implements Cloneable, Comparable<CmsUUID>, Externalizable {
064
065    /** A regular expression for matching UUIDs. */
066    public static final String UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
067
068    /** The log object for this class. */
069    private static final Log LOG = CmsLog.getLog(CmsUUID.class);
070
071    /** Ethernet address of the server machine. */
072    private static EthernetAddress m_ethernetAddress;
073
074    /** OpenCms UUID (name based uuid of "www.opencms.org" in the dns name space). */
075    private static UUID m_opencmsUUID = UUIDGenerator.getInstance().generateNameBasedUUID(
076        new UUID(UUID.NAMESPACE_DNS),
077        "www.opencms.org");
078
079    /** Constant for the null UUID. */
080    private static final CmsUUID NULL_UUID = new CmsUUID(UUID.getNullUUID());
081
082    /** Serial version UID required for safe serialization. */
083    private static final long serialVersionUID = 1736324454709298676L;
084
085    /** Internal UUID implementation. */
086    private transient UUID m_uuid;
087
088    /**
089     * Creates a new UUID.<p>
090     *
091     * Please note that the static init() method has to be called first to initialize the
092     * internet address of the machine.<p>
093     */
094    public CmsUUID() {
095
096        if (m_ethernetAddress == null) {
097            // if no ethernet address is available, generate a dummy
098            // this is required because otherwise we can't ever de-serialize a CmsUUID outside of OpenCms,
099            // since the empty constructor is called when the de-serialization takes place
100            init(CmsStringUtil.getEthernetAddress());
101        }
102        m_uuid = UUIDGenerator.getInstance().generateTimeBasedUUID(m_ethernetAddress);
103    }
104
105    /**
106     * Create a UUID based on a binary data array.<p>
107     *
108     * @param data a binary data array representing a UUID
109     */
110    public CmsUUID(byte[] data) {
111
112        m_uuid = new UUID(data);
113    }
114
115    /**
116     * Create a UUID based on a String.<p>
117     *
118     * @param uuid a String representing a UUID
119     * @throws NumberFormatException in case uuid is not a valid UUID
120     */
121    public CmsUUID(String uuid)
122    throws NumberFormatException {
123
124        m_uuid = new UUID(uuid);
125    }
126
127    /**
128     * Create a new UUID based on another one (used internal for cloning).<p>
129     *
130     * @param uuid the UUID to clone
131     */
132    private CmsUUID(UUID uuid) {
133
134        m_uuid = uuid;
135    }
136
137    /**
138     * Check that the given id is not the null id.<p>
139     *
140     * @param id the id to check
141     * @param canBeNull only if flag is set, <code>null</code> is accepted
142     *
143     * @see #isNullUUID()
144     */
145    public static void checkId(CmsUUID id, boolean canBeNull) {
146
147        if (canBeNull && (id == null)) {
148            return;
149        }
150        if ((!canBeNull && (id == null)) || id.isNullUUID()) {
151            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INVALID_UUID_1, id));
152        }
153    }
154
155    /**
156     * Returns a constant (name based) UUID,
157     * based on the given name in the OpenCms name space.
158     *
159     * @param name the name to derive the uuid from
160     * @return name based UUID of the given name
161     */
162    public static CmsUUID getConstantUUID(String name) {
163
164        return new CmsUUID(UUIDGenerator.getInstance().generateNameBasedUUID(m_opencmsUUID, name));
165    }
166
167    /**
168     * Returns a String representing a dummy (random based) ethernet address.<p>
169     *
170     * @return a String representing a dummy (random based) ethernet address
171     */
172    public static String getDummyEthernetAddress() {
173
174        return UUIDGenerator.getInstance().getDummyAddress().toString();
175    }
176
177    /**
178     * Returns a null UUID,
179     * use this null UUID to check if a UUID has been initialized or not.<p>
180     *
181     * @return a null UUID
182     */
183    public static CmsUUID getNullUUID() {
184
185        return NULL_UUID;
186    }
187
188    /**
189     * Returns a constant (name based) UUID for OpenCms,
190     * based on "www.opencms.org" in the dns name space.
191     *
192     * @return name based UUID of OpenCms
193     */
194    public static CmsUUID getOpenCmsUUID() {
195
196        return new CmsUUID(m_opencmsUUID);
197    }
198
199    /**
200     * Initialize the UUID generator with the ethernet address of the server machine.<p>
201     *
202     * The ethernetAddress parameter must represent a 'standard' ethernet MAC address string
203     * (e.g. '00:C0:F0:3D:5B:7C').
204     *
205     * @param ethernetAddress the ethernet address of the server machine
206     * @throws CmsInitException in case the ethernetAddress String is not a valid ethernet address
207     */
208    public static void init(String ethernetAddress) throws CmsInitException {
209
210        try {
211            m_ethernetAddress = new EthernetAddress(ethernetAddress);
212        } catch (Exception e) {
213            throw new CmsInitException(
214                Messages.get().container(Messages.ERR_INVALID_ETHERNET_ADDRESS_1, ethernetAddress));
215        }
216    }
217
218    /**
219     * Returns <code>true</code> if the given UUID is valid.<p>
220     *
221     * @param uuid the UUID to check
222     *
223     * @return <code>true</code> if the given UUID is valid
224     */
225    public static boolean isValidUUID(String uuid) {
226
227        try {
228            return (null != uuid) && (null != UUID.valueOf(uuid));
229        } catch (NumberFormatException e) {
230            // return false
231        }
232        return false;
233    }
234
235    /**
236     * Returns the given String transformed to a UUID in case the String is a valid UUID.<p>
237     *
238     * @param uuid the String to transform to a UUID
239     *
240     * @return the given String transformed to a UUID in case the String is a valid UUID
241     *
242     * @throws NumberFormatException in case the String is no valid UUID
243     */
244    public static CmsUUID valueOf(String uuid) throws NumberFormatException {
245
246        return new CmsUUID(UUID.valueOf(uuid));
247    }
248
249    /**
250     * Creates a clone of this CmsUUID.<p>
251     *
252     * @return a clone of this CmsUUID
253     */
254    @Override
255    public Object clone() {
256
257        if (this == NULL_UUID) {
258            return NULL_UUID;
259        }
260        return new CmsUUID((UUID)m_uuid.clone());
261    }
262
263    /**
264     * @see java.lang.Comparable#compareTo(Object)
265     */
266    public int compareTo(CmsUUID obj) {
267
268        return m_uuid.compareTo(obj.m_uuid);
269    }
270
271    /**
272     * @see java.lang.Object#equals(java.lang.Object)
273     */
274    @Override
275    public boolean equals(Object obj) {
276
277        if (obj == this) {
278            return true;
279        }
280        if (obj instanceof CmsUUID) {
281            return ((CmsUUID)obj).m_uuid.equals(m_uuid);
282        }
283        return false;
284    }
285
286    /**
287     * Returns the String representation of this UUID, same as {@link #toString()}.<p>
288     *
289     * This method is useful if bean like access to the UUID String is required.<p>
290     *
291     * @return the String representation of this UUID
292     */
293    public String getStringValue() {
294
295        return toString();
296    }
297
298    /**
299     * Optimized hashCode implementation for UUID's.<p>
300     *
301     * @see java.lang.Object#hashCode()
302     */
303    @Override
304    public int hashCode() {
305
306        return m_uuid.hashCode();
307    }
308
309    /**
310     * Returns true if this UUID is equal to the null UUID.<p>
311     *
312     * @return true if this UUID is equal to the null UUID
313     */
314    public boolean isNullUUID() {
315
316        if (this == NULL_UUID) {
317            return true;
318        }
319        return m_uuid.equals(UUID.getNullUUID());
320    }
321
322    /**
323     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
324     */
325    public void readExternal(ObjectInput in) {
326
327        Object o = null;
328        try {
329            o = in.readObject();
330        } catch (Throwable e) {
331            // there are 2 development version of OpenCms (6.1.7 and 6.1.8) which had a different format,
332            // here the Object was preceded by a Long
333            try {
334                // first read the long, we don't really need it but it must be removed from the stream
335                in.readLong();
336                o = in.readObject();
337            } catch (Throwable t) {
338                if (LOG.isDebugEnabled()) {
339                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_READ_UUID_OLD_1, o), t);
340                }
341            }
342        }
343
344        if (o instanceof String) {
345            // this UUID has been serialized using the new method
346            if (LOG.isDebugEnabled()) {
347                LOG.debug(Messages.get().getBundle().key(Messages.LOG_READ_UUID_1, o));
348            }
349            m_uuid = new UUID((String)o);
350        }
351
352        // log an error if the uuid could not be deserialized
353        if (m_uuid == null) {
354            // UUID cannot be deserialized
355            if (LOG.isDebugEnabled()) {
356                LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_READ_UUID_0));
357            }
358        }
359    }
360
361    /**
362     * Returns the UUID as a 16-byte byte array.<p>
363     *
364     * @return 16-byte byte array that contains the UUID's bytes in the network byte order
365     */
366    public byte[] toByteArray() {
367
368        return m_uuid.toByteArray();
369    }
370
371    /**
372     * @see java.lang.Object#toString()
373     */
374    @Override
375    public String toString() {
376
377        return m_uuid.toString();
378    }
379
380    /**
381     *
382     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
383     */
384    public void writeExternal(ObjectOutput out) throws IOException {
385
386        if (LOG.isDebugEnabled()) {
387            LOG.debug(Messages.get().getBundle().key(Messages.LOG_WRITE_UUID_1, toString()));
388        }
389        out.writeObject(toString());
390    }
391}