1 package org.starobjects.jpa.runtime.persistence.oid;
2
3 import static org.hamcrest.CoreMatchers.*;
4
5 import java.io.IOException;
6 import java.io.Serializable;
7 import java.text.NumberFormat;
8 import java.text.ParseException;
9 import java.util.HashMap;
10 import java.util.Map;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 import javax.persistence.DiscriminatorValue;
15
16 import org.nakedobjects.metamodel.adapter.oid.Oid;
17 import org.nakedobjects.metamodel.adapter.oid.stringable.directly.DirectlyStringableOid;
18 import org.nakedobjects.metamodel.commons.encoding.DataInputExtended;
19 import org.nakedobjects.metamodel.commons.encoding.DataOutputExtended;
20
21 import static org.nakedobjects.metamodel.commons.ensure.Ensure.*;
22 import org.nakedobjects.metamodel.spec.NakedObjectSpecification;
23 import org.nakedobjects.metamodel.specloader.NakedObjectReflector;
24 import org.nakedobjects.metamodel.util.ClassUtil;
25 import org.nakedobjects.runtime.context.NakedObjectsContext;
26 import org.nakedobjects.runtime.persistence.oidgenerator.OidGenerator;
27 import org.nakedobjects.runtime.persistence.services.ServiceUtil;
28 import org.starobjects.jpa.metamodel.facets.object.discriminator.JpaDiscriminatorValueFacet;
29 import org.starobjects.jpa.metamodel.facets.object.entity.JpaEntityFacet;
30 import org.starobjects.jpa.metamodel.specloader.DiscriminatorCache;
31
32
33 public final class JpaOid implements Oid, Cloneable, DirectlyStringableOid {
34
35 private static final long serialVersionUID = 1L;
36
37 private static final String DESTRING_IAE_EXCEPTION_MSG =
38 "Does not match format XXX|nnnn (persistent) or XXX|nnnn~ (transient)";
39 private static Pattern DESTRING_PATTERN = Pattern
40 .compile("^([A-Z]+)\\|([0-9]+)(~?)$");
41
42 private static Map<Class<?>,String> discriminatorByClass = new HashMap<Class<?>,String>();
43
44 private static String lookupDiscriminator(String className) {
45 try {
46 Class<?> cls = Thread.currentThread().getContextClassLoader().loadClass(className);
47 return lookupDiscriminator(cls);
48 } catch (ClassNotFoundException e) {
49 return className;
50 }
51 }
52
53 private static String lookupDiscriminator(Class<?> cls) {
54 String discriminator = discriminatorByClass.get(cls);
55 if (discriminator == null) {
56 discriminator = lookupAndStoreDiscriminator(cls, discriminator);
57 }
58 if (discriminator != null) {
59 return discriminator;
60 }
61
62 Class<?> superclass = cls.getSuperclass();
63 if (superclass == null) {
64 return cls.getName();
65 }
66 return lookupDiscriminator(superclass);
67 }
68
69 private static synchronized String lookupAndStoreDiscriminator(Class<?> cls,
70 String discriminator) {
71 DiscriminatorValue discriminatorValue = cls.getAnnotation(DiscriminatorValue.class);
72 if (discriminatorValue == null) {
73 return null;
74 }
75 discriminator = discriminatorValue.value();
76 discriminatorByClass.put(cls, discriminator);
77 return discriminator;
78 }
79
80 public static void testReset() {
81 discriminatorByClass.clear();
82 }
83
84 private String classname;
85 private Number id;
86 private Number newId;
87 private boolean isTransient;
88 private JpaOid previous;
89
90 private String discriminator;
91
92 private int cachedHashCode;
93 private String cachedToString;
94 private String cachedEnstring;
95
96
97 private static enum State {
98 TRANSIENT,
99 PERSISTENT;
100
101 public boolean isTransient() {
102 return this == TRANSIENT;
103 }
104
105 @SuppressWarnings("unused")
106 public boolean isPersistent() {
107 return this == PERSISTENT;
108 }
109
110 public static State decode(final boolean isTransient) {
111 return isTransient ? TRANSIENT : PERSISTENT;
112 }
113 }
114
115
116
117
118
119
120
121
122
123
124 public static JpaOid createTransient(
125 final Class<?> clazz,
126 final Number id) {
127 return createTransient(clazz.getName(), id);
128 }
129
130
131
132
133
134
135 public static JpaOid createTransient(
136 final String className,
137 final Number id) {
138 return createTransient(className, id, null);
139 }
140
141
142
143
144
145
146 public static JpaOid createTransient(
147 final String className,
148 final Number id,
149 final String discriminator) {
150 return new JpaOid(className, id, null, State.TRANSIENT, discriminator);
151 }
152
153
154
155
156
157
158 public static JpaOid createPersistent(
159 final Class<?> clazz,
160 final Number id) {
161 return createPersistent(clazz.getName(), id);
162 }
163
164
165
166
167
168
169 public static JpaOid createPersistent(
170 final String className,
171 final Number id) {
172 return createPersistent(className, id, null);
173 }
174
175
176
177
178
179
180 public static JpaOid createPersistent(
181 final String className,
182 final Number id,
183 final String discriminator) {
184 return new JpaOid(className, id, null, State.PERSISTENT, discriminator);
185 }
186
187
188
189
190
191
192
193
194 private JpaOid(
195 final String classname,
196 final Number id,
197 final Number newId,
198 final State state,
199 final String discriminator) {
200 this.classname = classname;
201 this.id = id;
202 this.newId = newId;
203 this.isTransient = state.isTransient();
204 this.discriminator = lookupDiscriminatorIfRequired(discriminator);
205 initialized();
206 }
207
208 private String lookupDiscriminatorIfRequired(final String discriminator) {
209 if (discriminator != null)
210 return discriminator;
211 else
212 return lookupDiscriminator(this.getClassName());
213 }
214
215
216 public JpaOid(final DataInputExtended input) throws IOException {
217 this.classname = input.readUTF();
218 this.id = input.readSerializable(Number.class);
219 this.isTransient = input.readBoolean();
220 this.newId = input.readSerializable(Number.class);
221 this.previous = input.readEncodable(JpaOid.class);
222 initialized();
223 }
224
225 public void encode(final DataOutputExtended output) throws IOException {
226 output.writeUTF(classname);
227 output.writeSerializable(id);
228 output.writeBoolean(isTransient);
229 output.writeSerializable(newId);
230 output.writeEncodable(previous);
231 }
232
233 private void initialized() {
234 cacheState();
235 }
236
237
238
239
240
241 public void copyFrom(final Oid oid) {
242 ensureThatArg(oid instanceof JpaOid, is(true));
243 final JpaOid from = (JpaOid) oid;
244 this.id = from.id;
245 this.classname = from.classname;
246 this.newId = from.newId;
247 this.isTransient = from.isTransient;
248 cacheState();
249 }
250
251
252
253
254
255
256
257
258 public String getClassName() {
259 return classname;
260 }
261
262
263
264
265
266
267
268
269
270 public Number getId() {
271 return id;
272 }
273
274 public String getDiscriminator() {
275 return discriminator;
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298 public void makePersistent() {
299 ensureThatState(isTransient(), is(true));
300 this.previous = new JpaOid(this.classname, this.id, null,
301 State.TRANSIENT, this.discriminator);
302
303 if (newId != null) {
304 id = newId;
305 }
306 this.isTransient = false;
307 newId = null;
308 cacheState();
309 }
310
311
312
313
314
315
316
317
318 public Number getNewId() {
319 return newId;
320 }
321
322
323
324
325
326
327
328 public void setNewId(final Number newId) {
329 this.newId = newId;
330 cacheState();
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345 public Oid getPrevious() {
346 return previous;
347 }
348
349 public boolean hasPrevious() {
350 return previous != null;
351 }
352
353 public void clearPrevious() {
354 previous = null;
355 }
356
357
358
359
360
361
362 public boolean isTransient() {
363 return isTransient;
364 }
365
366
367
368
369
370
371
372 public static JpaOid deString(final String oidStr) {
373 final NakedObjectReflector reflector =
374 (NakedObjectReflector) NakedObjectsContext
375 .getSpecificationLoader();
376 final DiscriminatorCache discriminatorCache =
377 (DiscriminatorCache) reflector.getMetaModelValidator();
378 return deString(oidStr, discriminatorCache);
379 }
380
381 public static JpaOid deString(final String oidStr,
382 final DiscriminatorCache discriminatorCache) {
383 if (oidStr == null) {
384 return null;
385 }
386 final Matcher matcher = DESTRING_PATTERN.matcher(oidStr);
387 if (!matcher.matches()) {
388 throw new IllegalArgumentException(DESTRING_IAE_EXCEPTION_MSG);
389 }
390 final String discriminator = matcher.group(1);
391 final NakedObjectSpecification noSpec = discriminatorCache
392 .lookupClassFor(discriminator);
393 if (noSpec == null) {
394 throw new IllegalArgumentException("Unknown discriminator '" + discriminator + "'");
395 }
396 final String className = noSpec.getFullName();
397
398 final String idStr = matcher.group(2);
399 Number id;
400 try {
401 id = NumberFormat.getInstance().parse(idStr);
402 } catch (final ParseException e) {
403 throw new IllegalArgumentException(DESTRING_IAE_EXCEPTION_MSG);
404 }
405
406 final String transientStr = matcher.group(3);
407 return transientStr != null ? JpaOid.createTransient(className, id, discriminator)
408 : JpaOid.createPersistent(className, id, discriminator);
409 }
410
411 public String enString() {
412 return cachedEnstring;
413 }
414
415
416
417
418
419
420 @Override
421 public boolean equals(final Object other) {
422 if (other == this) {
423 return true;
424 }
425 if (getClass() != other.getClass()) {
426 return false;
427 }
428 return equals((JpaOid) other);
429 }
430
431 public boolean equals(final JpaOid other) {
432 return isTransient == other.isTransient &&
433 classname.equals(other.classname) &&
434 id.equals(other.id);
435 }
436
437
438
439
440
441
442 @Override
443 public int hashCode() {
444 return cachedHashCode;
445 }
446
447 private void cacheState() {
448 cachedHashCode = 17;
449 cachedHashCode = 37 * cachedHashCode + classname.hashCode();
450 cachedHashCode = 37 * cachedHashCode + id.hashCode();
451
452 cachedToString = (isTransient() ? "T" : "") + "HOID#" + id.toString()
453 + "/" + classname
454 + (newId == null ? "" : "(" + newId + ")")
455 + (previous == null ? "" : "+");
456
457 cachedEnstring = discriminator + "|" + this.id + (isTransient?"~":"");
458 }
459
460
461
462
463
464
465 @Override
466 public JpaOid clone() {
467 final JpaOid jpaOid = new JpaOid(this.classname, this.id, this.newId,
468 State.decode(this.isTransient), this.discriminator);
469 jpaOid.previous = this.previous;
470 return jpaOid;
471 }
472
473
474
475
476
477
478 @Override
479 public String toString() {
480 return cachedToString;
481 }
482
483
484 }