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.io.InputStream;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import javax.xml.parsers.SAXParser;
26  import javax.xml.parsers.SAXParserFactory;
27  
28  import org.xml.sax.Attributes;
29  import org.xml.sax.SAXException;
30  import org.xml.sax.helpers.DefaultHandler;
31  
32  /**
33   * <p>The <code>ReadOnlyResourceStateImpl</code> is an implementation of the
34   * <code>State</code> interface. This implementation provides better guarantees
35   * that no duplicate UUID's will be generated; however since the only stateful
36   * information provided is the IEEE 802 address the generator should use a
37   * better choice is to use an implementation that also writes to persistent
38   * storage each time the state is loaded a new clock sequence is used. If the
39   * system time is adjusted backwards there is a possibility that a UUID generated
40   * with the same clock sequence and time could be generated.
41   *
42   * @author Commons-Id team
43   * @version $Id: ReadOnlyResourceStateImpl.java 480488 2006-11-29 08:57:26Z bayard $
44   */
45  public class ReadOnlyResourceStateImpl implements State {
46  
47      /** How often to write to stable storage - since this is read-only make it largest. */
48      static long synchronizeInterval = Long.MAX_VALUE;
49  
50      /** Collection of nodes to load or store. */
51      static HashSet nodes = new HashSet();
52  
53      /**
54       * The key to use in locating the uuid configuration xml file from System
55       * properties.
56       */
57      public static final String CONFIG_FILENAME_KEY = "org.apache.commons.id.uuid.config.resource.filename";
58  
59      /**
60       * <p>Constructs a ReadOnlyResouceStateImpl.</p>
61       */
62      public ReadOnlyResourceStateImpl() {
63          super();
64      }
65  
66      /**
67       * <p>Loads the System.property &quot;commons.uuid.configFileName&quot;
68       * (default is &quot;uuid.conf&quot;) using commons.discovery.</p>
69       * <p>
70       * The uuid-[n].conf file is an xml file with the following syntax:<br>
71       * <pre>
72       * <?xml version="1.0" encoding="UTF-8" ?>
73       * <!DOCTYPE uuidstate [
74       * <!ELEMENT uuidstate (node*)>
75       * <!ELEMENT node EMPTY>
76       * <!ATTLIST node id ID #REQUIRED>
77       * <!ATTLIST node clocksequence CDATA #IMPLIED>
78       * <!ATTLIST node lasttimestamp CDATA #IMPLIED>
79       * ]>
80       * <uuidstate>
81       * <node id="XX-XX-XX-XX-XX-XX" />
82       * <node id="YY-YY-YY-YY-YY-YY" />
83       * </uuidstate>
84       * </pre>
85       * </p><p>See the documentation for further information on configuration
86       * tasks.</p>
87       *
88       * @throws IllegalStateException if the &quot;commons.uuid.configFileName&quot;
89       * system property is not set or the resource cannot be loaded.
90       * @throws SAXException if an xml parsing error occurs
91       * @throws ParserConfigurationException if the parser cannot be loaded
92       * @throws IOException if an error occurs reading the file
93       * 
94       * @see org.apache.commons.id.uuid.state.State#load()
95       */
96      public void load() throws Exception {
97          // Get the resource name
98          String resourceName = System.getProperty(CONFIG_FILENAME_KEY);
99          if (resourceName == null) {
100             throw new IllegalStateException("No value set for system property: " 
101                     + CONFIG_FILENAME_KEY);
102         }
103 
104         // Load the resource
105         InputStream in = null;
106         try {
107             in = ClassLoader.getSystemResourceAsStream(resourceName);
108             if (in == null) {
109                 throw new IllegalStateException(resourceName + 
110                         " loaded as system resource is null");
111             }
112             //Do the XML parsing
113             parse(in);
114         } finally {
115             if (in != null) {
116                 try {
117                     in.close();
118                 } catch (IOException ioe) {
119                     //Nothing to do at this point.
120                 }
121             }
122         }
123     }
124 
125     /**
126      * @see State#getSynchInterval
127      */
128     public long getSynchInterval() {
129         //Return Long.MAX_VALUE since this is readonly.
130         return Long.MAX_VALUE;
131     }
132 
133     /**
134      * @see org.apache.commons.id.uuid.state.State#getNodes()
135      */
136     public Set getNodes() {
137         return nodes;
138     }
139 
140     /**
141      * @see org.apache.commons.id.uuid.state.State#store(java.util.Set)
142      */
143     public void store(Set nodeSet) throws IOException {
144         // Nothing to do - this is a ReadOnly implementation.
145         return;
146     }
147 
148     /**
149      * @see org.apache.commons.id.uuid.state.State#store(java.util.Set, long)
150      */
151     public void store(Set nodeSet, long timestamp) {
152         // Nothing to do - this is a ReadOnly implementation.
153         return;
154     }
155 
156     /**
157      * <p>Parses the XML configuration into the <code>Node</code>s and places
158      * into this instances node collection.</p>
159      *
160      * @param in the XML input stream to parse.
161      */
162     protected void parse(InputStream in) throws Exception {
163         DefaultHandler handler = new StateConfigHandler();
164         SAXParserFactory saxFactory = SAXParserFactory.newInstance();
165         saxFactory.setValidating(true);
166         SAXParser parser = saxFactory.newSAXParser();
167         parser.parse(in, handler);
168     }
169    
170     //--------------------------------------------------------------------------
171     /**
172      * Inner class to handle document processing of the configuration file.
173      */
174     class StateConfigHandler extends DefaultHandler {
175         /** Constant for the uuidstate tag */
176         static final short UUID_STATE_TAG = 1;
177         /** Constant string value for uuidstate tag */
178         static final String UUID_STATE_TAG_STR = "uuidstate";
179         /** Constant string value for the synchinterval attribute */
180         static final String SYNCH_INTERVAL_STR = "synchinterval";
181         /** Constant for the node tag */
182         static final short NODE_TAG = 2;
183         /** Constant string value for the node tag */
184         static final String NODE_TAG_STR = "node";
185         /** Constant string value for the id attribute */
186         static final String ATTR_ID_STR = "id";
187         /** Constant string value for the last clock sequence attribute */
188         static final String ATTR_CLOCKSEQ_STR = "clocksequence";
189         /** Constant string value for the last time stamp attribute */
190         static final String ATTR_LASTIMESTAMP_STR = "timestamp";
191 
192         /**
193          * Handle start of tag.
194          * @see org.xml.sax.helpers.DefaultHandler#startElement(String, String, String, Attributes)
195          */
196         public void startElement(
197             String namespaceURI,
198             String simpleName,
199             String qualifiedName,
200             Attributes attributes)
201             throws SAXException {
202 
203             short currentTag = 0;
204 
205             String element = simpleName;
206             if ("".equals(simpleName)) {
207                 element = qualifiedName;
208             }
209             if (element.equalsIgnoreCase(UUID_STATE_TAG_STR)) {
210                 currentTag = UUID_STATE_TAG;
211             } else if (element.equalsIgnoreCase(NODE_TAG_STR)) {
212                 currentTag = NODE_TAG;
213             }
214             //Process attributes
215             if (attributes != null) {
216                 switch (currentTag) {
217                     case 1 :
218                         processBodyTag(attributes);
219                         break;
220                     case 2 :
221                         processNodeTag(attributes);
222                         break;
223                     default :
224                         break;
225                 }
226             }
227         }
228 
229         /**
230          * <p>Processes the main body tag of document.</p>
231          *
232          * @param attributes - sax Attributes to process.
233          */
234         private void processBodyTag(Attributes attributes) {
235             for (int i = 0; i < attributes.getLength(); i++) {
236                 String attributeName = attributes.getLocalName(i);
237                 if ("".equals(attributeName)) {
238                     attributeName = attributes.getQName(i);
239                 }
240                 String attributeValue = attributes.getValue(i);
241                 if (attributeName.equalsIgnoreCase(SYNCH_INTERVAL_STR)) {
242                     try {
243                         synchronizeInterval = Long.parseLong(attributeValue);
244                     } catch (NumberFormatException nfe) {
245                         synchronizeInterval = 0;
246                     }
247                 }
248             }
249         }
250 
251         /**
252          * <p>Process a node tag</p>
253          *
254          * @param attributes - sax Attributes to process.
255          */
256         private void processNodeTag(Attributes attributes) {
257             byte[] node = null;
258             long lastTS = 0;
259             short lastClockSeq = 0;
260             for (int i = 0; i < attributes.getLength(); i++) {
261                 String attributeName = attributes.getLocalName(i);
262                 if ("".equals(attributeName)) {
263                     attributeName = attributes.getQName(i);
264                 }
265                 String attributeValue = attributes.getValue(i);
266 
267                 if (attributeName.equalsIgnoreCase(ATTR_ID_STR)) {
268                     node = StateHelper.decodeMACAddress(attributeValue);
269                 } else if (attributeName.equalsIgnoreCase(ATTR_CLOCKSEQ_STR)) {
270                     try {
271                         lastClockSeq = Short.parseShort(attributeValue);
272                     } catch (NumberFormatException nfe) {
273                         lastClockSeq = 0;
274                     }
275                 } else if ( attributeName.equalsIgnoreCase(ATTR_LASTIMESTAMP_STR)) {
276                     try {
277                         lastTS = Long.parseLong(attributeValue);
278                     } catch (NumberFormatException nfe) {
279                         lastTS = 0;
280                     }
281                 }
282             }
283             if (node != null) {
284                 if (lastClockSeq != 0) {
285                     nodes.add(new Node(node, lastTS, lastClockSeq));
286                 } else {
287                     nodes.add(new Node(node));
288                 }
289             }
290         }
291     }
292     //--------------------------------------------------------------------------
293 }