View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.id.uuid.state;
19  
20  import java.io.IOException;
21  import java.net.InetAddress;
22  import java.net.UnknownHostException;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.Random;
26  import java.util.StringTokenizer;
27  
28  import org.apache.commons.id.DecoderException;
29  import org.apache.commons.discovery.tools.DiscoverClass;
30  import org.apache.commons.id.uuid.Bytes;
31  import org.apache.commons.id.uuid.Constants;
32  import org.apache.commons.id.uuid.clock.Clock;
33  import org.apache.commons.id.uuid.clock.OverClockedException;
34  import org.apache.commons.id.DigestUtils;
35  import org.apache.commons.id.Hex;
36  
37  /**
38   * <p><code>StateHelper</code> provides helper methods for the uuid state
39   * implementations.</p>
40   *
41   * @author Commons-Id team
42   * @version $Id: StateHelper.java 480488 2006-11-29 08:57:26Z bayard $
43   */
44  public final class StateHelper implements Constants {
45      /** The key for the System.property containing the ClockImpl String. */
46      public static final String UUID_CLOCK_IMPL_PROPERTY_KEY = Clock.class.getName();
47  
48      /** The key for the System.property containing the StateImpl String. */
49      public static final String UUID_STATE_IMPL_PROPERTY_KEY = State.class.getName();
50  
51      /** Array length of node bytes. */
52      public static final int NODE_ID_BYTE_LENGTH = 6;
53  
54      /** Number of bytes in a short. */
55      public static final short BYTES_IN_SHORT = 2;
56  
57      /** Number of postitions to shift when shifting by one byte. */
58      public static final short SHIFT_BY_BYTE = 8;
59  
60      /** Number of postitions to shift when shifting by one byte. */
61      public static final short HOSTNAME_MAX_CHAR_LEN = 255;
62  
63      /** OR-Mask to set the node's multicast bit true. */
64      private static final int MULTICAST_BIT_SET = 0x80;
65  
66      /** The maximum character length of a long */
67      private static final short LONG_CHAR_LEN = 19;
68  
69      /** Standard one page buffer size */
70      private static final int BUF_PAGE_SZ = 1024;
71  
72      /** Start of the XML document used to store state persistence in XML */
73      protected static final String XML_DOC_START = "<?xml version=\"1.0\""
74          + " encoding=\"UTF-8\" ?>"
75          + "\n<!DOCTYPE uuidstate [\n"
76          + "   <!ELEMENT uuidstate (node*)>\n"
77          + "   <!ELEMENT node EMPTY>\n"
78          + "   <!ATTLIST node id ID #REQUIRED>\n"
79          + "   <!ATTLIST node clocksequence CDATA #IMPLIED>\n"
80          + "   <!ATTLIST node lasttimestamp CDATA #IMPLIED>\n]>"
81          + "\n<uuidstate synchInterval=\"";
82  
83      /** End of document start */
84      protected static final String XML_DOC_START_END = "\">";
85      /** Start of XML node tag */
86      protected static final String XML_NODE_TAG_START = "\n\t<node id=\"";
87      /** After id of XML node tag */
88      protected static final String XML_NODE_TAG_AFTER_ID = "\" clocksequence=\"";
89      /** After clock sequence of XML node tag */
90      protected static final String XML_NODE_TAG_AFTER_CSEQ = "\" timestamp=\"";
91      /** End of XML node tag */
92      protected static final String XML_NODE_TAG_END = "\" />";
93      /** End of the XML document used to store state persistence in XML */
94      protected static final String XML_DOC_END = "\n</uuidstate>";
95  
96      /** Number of tokens in a MAC address with "-" as separator */
97      private static final short MAC_ADDRESS_TOKEN_COUNT = 6;
98  
99      /** String length of a MAC address with "-" as separator */
100     private static final short MAC_ADDRESS_CHAR_LENGTH = 17;
101 
102     /** Default constructor */
103     private StateHelper() {
104         super();
105     }
106 
107     /**
108      * <p>Creates a Random node identifier as described in IEFT UUID URN
109      * specification.</p>
110      *
111      * @return a random node idenfifier based on MD5 of system information.
112      */
113     public static byte[] randomNodeIdentifier() {
114         //Holds the 16 byte MD5 value
115         byte[] seed = new byte[UUID_BYTE_LENGTH];
116         //Set the initial string buffer capacity
117         //Time + Object.hashCode + HostName + Guess of all system properties
118         int bufSize = (LONG_CHAR_LEN * 2) + HOSTNAME_MAX_CHAR_LEN + (2 * BUF_PAGE_SZ);
119         StringBuffer randInfo = new StringBuffer(bufSize);
120         //Add current time
121         long time = 0;
122         try {
123             time = getClockImpl().getUUIDTime();
124         } catch (OverClockedException oce) {
125             time = System.currentTimeMillis();
126         }
127         randInfo.append(time);
128 
129         //Add hostname
130         try {
131             InetAddress address = InetAddress.getLocalHost();
132             randInfo.append(address.getHostName());
133         } catch (UnknownHostException ukhe) {
134             randInfo.append("Host Unknown");
135         }
136         //Add something else "random"
137         randInfo.append(new Object().hashCode());
138 
139         //Add system properties
140         Collection info = System.getProperties().values();
141         Iterator it = info.iterator();
142         while (it.hasNext()) {
143             randInfo.append(it.next());
144         }
145         //MD5 Hash code the system information to get a node id.
146         seed = DigestUtils.md5(randInfo.toString());
147 
148         //Return upper 6 bytes of hash
149         byte[] raw = new byte[NODE_ID_BYTE_LENGTH];
150         System.arraycopy(seed, 0, raw, 0, NODE_ID_BYTE_LENGTH);
151 
152         //Per draft set multi-cast bit true
153         raw[0] |= MULTICAST_BIT_SET;
154 
155         return raw;
156     }
157 
158     /**
159      * <p>Generates a new security quality random clock sequence.</p>
160      *
161      * @return a new security quality random clock sequence.
162      */
163     public static short newClockSequence() {
164         Random random = new Random();
165         byte[] bytes = new byte[BYTES_IN_SHORT];
166         random.nextBytes(bytes);
167         return (short) (Bytes.toShort(bytes) & 0x3FFF);
168     }
169 
170     /**
171      * <p>Returns the Clock implementation using commons discovery.</p>
172      *
173      * @return the Clock implementation using commons discovery.
174      */
175     public static Clock getClockImpl() {
176         Clock c = null;
177         try {
178              DiscoverClass dc = new DiscoverClass();
179              c = (Clock) dc.newInstance(
180              Clock.class,
181              Clock.DEFAULT_CLOCK_IMPL);
182         } catch (Exception ex) {
183              // ignore as default implementation will be used.
184         }
185         return c;
186     }
187 
188     /**
189      * <p>Returns the <code>State</code> implementation in use.</p>
190      *
191      * @return the <code>State</code> implementation in use.
192      */
193     public static State getStateImpl() {
194         State s = null;
195         try {
196              DiscoverClass dc = new DiscoverClass();
197              s = (State) dc.newInstance(
198              State.class,
199              State.DEFAULT_STATE_IMPL);
200         } catch (Exception ex) {
201              // ignore as default implementation will be used.
202         }
203         return s;
204     }
205 
206     /**
207      * <p>Utility method decodes a valid MAC address in the form of
208      * XX-XX-XX-XX-XX-XX where each XX represents a hexidecimal value.</p>
209      * 
210      * <p> Returns null if the address can not be decoded. </p>
211      *
212      * @param address the String hexidecimal dash separated MAC address.
213      * @return a byte array representing the the address. Null if not a valid address.
214      */
215     public static byte[] decodeMACAddress(String address) {
216         StringBuffer buf = new StringBuffer(MAC_ADDRESS_TOKEN_COUNT * 2);
217         StringTokenizer tokens = new StringTokenizer(address, "-");
218         if (tokens.countTokens() != MAC_ADDRESS_TOKEN_COUNT) {
219             return null;
220         } else {
221             for (int i = 0; i < MAC_ADDRESS_TOKEN_COUNT; i++) {
222                 buf.append(tokens.nextToken());
223             }
224         }
225         try {
226             char[] c = buf.toString().toCharArray();
227             return Hex.decodeHex(c);
228         } catch (DecoderException de) {
229             de.printStackTrace();
230             return null;
231         }
232     }
233 
234     /**
235      * <p>Returns the node id / address byte array in it's hexidecimal string
236      * representation in the following format: XX-XX-XX-XX-XX-XX where each XX
237      * represents a hexidecimal value.</p>
238      *
239      * @param address the 6 byte node id / address.
240      * @return the node id /address byte array in as hexidecimal with dash
241      * separating each octet.
242      * @throws IOException an Input Output Exception.
243      */
244     public static String encodeMACAddress(byte[] address) throws IOException {
245        char[] chars = Hex.encodeHex(address);
246        StringBuffer buf = new StringBuffer(MAC_ADDRESS_CHAR_LENGTH);
247        for (int i = 0; i < chars.length; i++) {
248                buf.append(chars[i]);
249                if (i != chars.length - 1 && i % 2 != 0) {
250                    buf.append("-");
251                }
252        }
253        return buf.toString().toUpperCase();
254     }
255 }