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 "commons.uuid.configFileName"
68 * (default is "uuid.conf") 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 "commons.uuid.configFileName"
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 }