1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.river.outrigger.proxy;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.DataOutputStream;
22 import java.io.IOException;
23 import java.io.InvalidObjectException;
24 import java.io.ObjectInput;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.io.Serializable;
28 import java.lang.reflect.Constructor;
29 import java.lang.reflect.Field;
30 import java.lang.reflect.Modifier;
31 import java.net.MalformedURLException;
32 import java.rmi.MarshalException;
33 import java.rmi.UnmarshalException;
34 import java.security.DigestOutputStream;
35 import java.security.MessageDigest;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Comparator;
39 import java.util.WeakHashMap;
40 import java.util.logging.Logger;
41 import net.jini.core.entry.Entry;
42 import net.jini.core.entry.UnusableEntryException;
43 import net.jini.id.Uuid;
44 import net.jini.id.UuidFactory;
45 import net.jini.io.MarshalledInstance;
46 import net.jini.space.JavaSpace;
47 import org.apache.river.api.io.AtomicSerial;
48 import org.apache.river.api.io.AtomicSerial.GetArg;
49 import org.apache.river.api.io.AtomicSerial.ReadInput;
50 import org.apache.river.api.io.AtomicSerial.ReadObject;
51 import org.apache.river.landlord.LeasedResource;
52 import org.apache.river.logging.Levels;
53 import org.apache.river.proxy.CodebaseProvider;
54 import org.apache.river.proxy.MarshalledWrapper;
55
56
57
58
59
60
61
62
63
64
65
66 @AtomicSerial
67 public class EntryRep implements StorableResource<EntryRep>, LeasedResource, Serializable {
68 static final long serialVersionUID = 3L;
69
70
71
72
73
74
75
76
77
78
79 private volatile MarshalledInstance[] values;
80
81 private volatile String[] superclasses;
82 private volatile long[] hashes;
83 private volatile long hash;
84 private volatile String className;
85 private volatile String codebase;
86 private volatile Uuid id;
87 private volatile transient long expires;
88
89
90
91
92
93 private volatile transient boolean integrity;
94
95
96 private static final FieldComparator comparator = new FieldComparator();
97
98
99
100
101
102
103
104 private static final EntryRep matchAnyRep;
105
106 static {
107 classHashes = new WeakHashMap<Class,Long>();
108 try {
109 matchAnyRep = new EntryRep(new Entry() {
110
111 static final long serialVersionUID = -4244768995726274609L;
112 }, false);
113 } catch (MarshalException e) {
114 throw new AssertionError(e);
115 }
116 }
117
118
119
120
121
122
123
124
125 private volatile transient Class realClass;
126
127
128
129
130
131
132 private static final Logger logger =
133 Logger.getLogger("org.apache.river.outrigger.proxy");
134
135
136
137
138
139
140
141
142
143
144
145
146 public synchronized void shareWith(EntryRep other) {
147 className = other.className;
148 superclasses = other.superclasses;
149 hashes = other.hashes;
150 hash = other.hash;
151 }
152
153
154
155
156
157
158
159
160 static private Field[] getFields(Class cl) {
161 final Field[] fields = cl.getFields();
162 Arrays.sort(fields, comparator);
163 return fields;
164 }
165
166
167
168
169
170 static final private WeakHashMap<Class,Long> classHashes;
171
172
173
174
175
176
177 static synchronized private Long findHash(Class clazz,
178 boolean marshaling)
179 throws MarshalException, UnusableEntryException
180 {
181
182 Long hash = classHashes.get(clazz);
183
184
185
186
187 if (hash == null) {
188 try {
189 Field[] fields = getFields(clazz);
190 MessageDigest md = MessageDigest.getInstance("SHA");
191 DataOutputStream out =
192 new DataOutputStream(
193 new DigestOutputStream(new ByteArrayOutputStream(127),
194 md));
195 Class c = clazz.getSuperclass();
196 if (c != Object.class)
197
198 out.writeLong(findHash(c, marshaling).longValue());
199
200
201
202
203
204 for (int i = 0; i < fields.length; i++) {
205 if (!usableField(fields[i]))
206 continue;
207 out.writeUTF(fields[i].getName());
208 out.writeUTF(fields[i].getType().getName());
209 }
210 out.flush();
211 byte[] digest = md.digest();
212 long h = 0;
213 for (int i = Math.min(8, digest.length); --i >= 0; ) {
214 h += ((long)(digest[i] & 0xFF)) << (i * 8);
215 }
216 hash = Long.valueOf(h);
217 } catch (Exception e) {
218 if (marshaling)
219 throw throwNewMarshalException(
220 "Exception calculating entry class hash for " +
221 clazz, e);
222 else
223 throw throwNewUnusableEntryException(
224 "Exception calculating entry class hash for " +
225 clazz, e);
226 }
227 classHashes.put(clazz, hash);
228 }
229 return hash;
230 }
231
232
233
234
235
236
237
238
239
240 private EntryRep(Entry entry, boolean validate) throws MarshalException {
241 realClass = entry.getClass();
242 if (validate)
243 ensureValidClass(realClass);
244 className = realClass.getName();
245 codebase = CodebaseProvider.getClassAnnotation(realClass);
246
247
248
249
250
251 final Field[] fields = getFields(realClass);
252 int numFields = fields.length;
253
254
255 MarshalledInstance[] vals = new MarshalledInstance[numFields];
256 int nvals = 0;
257
258 for (int fnum = 0; fnum < fields.length; fnum++) {
259 final Field field = fields[fnum];
260 if (!usableField(field))
261 continue;
262
263 final Object fieldValue;
264 try {
265 fieldValue = field.get(entry);
266 } catch (IllegalAccessException e) {
267
268
269
270
271
272 throw throwRuntime(
273 new IllegalArgumentException("Couldn't access field "
274 + field, e)
275 );
276 }
277
278 if (fieldValue == null) {
279 vals[nvals] = null;
280 } else {
281 try {
282 vals[nvals] = new MarshalledInstance(fieldValue);
283 } catch (IOException e) {
284 throw throwNewMarshalException(
285 "Can't marshal field " + field + " with value " +
286 fieldValue, e);
287 }
288 }
289
290 nvals++;
291 }
292
293
294 MarshalledInstance [] values = new MarshalledInstance[nvals];
295 System.arraycopy(vals, 0, values, 0, nvals);
296 this.values = values;
297
298 try {
299 hash = findHash(realClass, true).longValue();
300 } catch (UnusableEntryException e) {
301
302 throw new AssertionError(e);
303 }
304
305
306 ArrayList<String> sclasses = new ArrayList<String>();
307 ArrayList<Long> shashes = new ArrayList<Long>();
308 for (Class c = realClass.getSuperclass();
309 c != Object.class;
310 c = c.getSuperclass())
311 {
312 try {
313 sclasses.add(c.getName());
314 shashes.add(findHash(c, true));
315 } catch (ClassCastException cce) {
316 break;
317 } catch (UnusableEntryException e) {
318
319 throw new AssertionError(e);
320 }
321 }
322 superclasses = sclasses.toArray(new String[sclasses.size()]);
323 long [] hashes = new long[shashes.size()];
324 for (int i=0; i < hashes.length; i++) {
325 hashes[i] = (shashes.get(i)).longValue();
326 }
327 this.hashes = hashes;
328 }
329
330
331
332
333
334 public EntryRep(Entry entry) throws MarshalException {
335 this(entry, true);
336 }
337 private static boolean checkIntegrity(GetArg arg) throws IOException {
338 MarshalledInstance[] values = (MarshalledInstance[]) arg.get("values", null);
339 if (values == null) throw new InvalidObjectException("null values");
340 String[] superclasses = (String[]) arg.get("superclasses", null);
341 if (superclasses == null) throw new InvalidObjectException("null superclasses");
342 long[] hashes = (long[]) arg.get("hashes", null);
343 if (hashes == null) throw new InvalidObjectException("null hashes");
344 if (hashes.length != superclasses.length)
345 throw new InvalidObjectException("hashes.length (" +
346 hashes.length + ") does not equal superclasses.length (" +
347 superclasses.length + ")");
348 arg.get("hash", 0L);
349 String className = (String) arg.get("className", null);
350 if (className == null) throw new InvalidObjectException("null className");
351 Object codebase = arg.get("codebase", null);
352 if (codebase != null && !((codebase instanceof String))) throw
353 new InvalidObjectException("codebase must be an instance of string");
354 Object id = arg.get("id", null);
355 if (id != null && !((id instanceof Uuid))) throw
356 new InvalidObjectException("id must be an instance of Uuid");
357 return ((RO) arg.getReader()).integrity;
358 }
359
360 private EntryRep(GetArg arg, boolean integrity) throws IOException {
361 values = (MarshalledInstance[]) arg.get("values", null);
362 superclasses = (String[]) arg.get("superclasses", null);
363 hashes = (long[]) arg.get("hashes", null);
364 hash = arg.get("hash", 0L);
365 className = (String) arg.get("className", null);
366 codebase = (String) arg.get("codebase", null);
367 id = (Uuid) arg.get("id", null);
368 this.integrity = integrity;
369 }
370
371 EntryRep(GetArg arg) throws IOException {
372 this(arg, checkIntegrity(arg));
373 }
374
375
376
377 public EntryRep() { }
378
379 @ReadInput
380 private static ReadObject getRO() {
381 return new RO();
382 }
383
384 private static class RO implements ReadObject {
385
386 boolean integrity;
387
388 @Override
389 public void read(ObjectInput input) throws IOException, ClassNotFoundException {
390
391 integrity = MarshalledWrapper.integrityEnforced((ObjectInputStream)input);
392 }
393
394 }
395
396
397
398 private final static Class[] noArg = new Class[0];
399
400
401
402
403
404 private static void ensureValidClass(Class c) {
405 boolean ctorOK = false;
406 try {
407 if (!Modifier.isPublic(c.getModifiers())) {
408 throw throwRuntime(new IllegalArgumentException(
409 "entry class " + c.getName() + " not public"));
410 }
411 Constructor ctor = c.getConstructor(noArg);
412 ctorOK = Modifier.isPublic(ctor.getModifiers());
413 } catch (NoSuchMethodException e) {
414 ctorOK = false;
415 } catch (SecurityException e) {
416 ctorOK = false;
417 }
418 if (!ctorOK) {
419 throw throwRuntime(new IllegalArgumentException("entry class " +
420 c.getName() +" needs public no-arg constructor"));
421 }
422 }
423
424
425
426
427
428
429 public static EntryRep matchAnyEntryRep() {
430 return matchAnyRep;
431 }
432
433
434
435
436
437 private static boolean isMatchAny(EntryRep rep) {
438 return matchAnyRep.equals(rep);
439 }
440
441
442
443
444 public static String matchAnyClassName() {
445 return matchAnyRep.classFor();
446 }
447
448
449
450
451
452
453
454
455
456
457
458 public Entry entry() throws UnusableEntryException {
459 ObjectInputStream objIn = null;
460 String className = "";
461 try {
462 ArrayList badFields = null;
463 ArrayList except = null;
464 Entry entryObj = null;
465 int valuesLength = 0;
466 int nvals = 0;
467
468 synchronized (this){
469 className = this.className;
470 realClass = CodebaseProvider.loadClass(codebase, className,
471 null, integrity, null);
472
473 if (findHash(realClass, false).longValue() != hash)
474 throw throwNewUnusableEntryException(
475 new IncompatibleClassChangeError(realClass + " changed"));
476
477 entryObj = (Entry) realClass.newInstance();
478
479 Field[] fields = getFields(realClass);
480
481
482
483
484
485
486 int fLength = fields.length;
487 valuesLength = values.length;
488 for (int i = 0; i < fLength; i++) {
489 Throwable nested = null;
490 try {
491 if (!usableField(fields[i]))
492 continue;
493
494 final MarshalledInstance val = values[nvals++];
495 Object value = (val == null ? null : val.get(integrity));
496 fields[i].set(entryObj, value);
497 } catch (Throwable e) {
498 nested = e;
499 }
500
501 if (nested != null) {
502 if (badFields == null) {
503 badFields = new ArrayList(fLength);
504 except = new ArrayList(fLength);
505 }
506 badFields.add(fields[i].getName());
507 except.add(nested);
508 }
509 }
510 }
511
512
513
514
515
516
517 if (nvals < valuesLength) {
518 throw throwNewUnusableEntryException(
519 entryObj,
520 null,
521 new Throwable[] {
522 new IncompatibleClassChangeError(
523 "A usable field has been removed from " +
524 entryObj.getClass().getName() +
525 " since this EntryRep was created")
526 });
527 }
528
529
530 if (badFields != null) {
531 String[] bf =
532 (String[]) badFields.toArray(
533 new String[badFields.size()]);
534 Throwable[] ex =
535 (Throwable[]) except.toArray(new Throwable[bf.length]);
536 throw throwNewUnusableEntryException(entryObj, bf, ex);
537 }
538
539
540 return entryObj;
541 } catch (InstantiationException e) {
542
543
544
545
546
547 throw throwNewUnusableEntryException(e);
548 } catch (ClassNotFoundException e) {
549
550 throw throwNewUnusableEntryException("Encountered a " +
551 "ClassNotFoundException while unmarshalling " + className, e);
552 } catch (IllegalAccessException e) {
553
554 throw throwNewUnusableEntryException(e);
555 } catch (RuntimeException e) {
556
557 throw throwNewUnusableEntryException("Encountered a " +
558 "RuntimeException while unmarshalling " + className, e);
559 } catch (MalformedURLException e) {
560
561 throw throwNewUnusableEntryException("Malformed URL " +
562 "associated with entry of type " + className, e);
563 } catch (MarshalException e) {
564
565 throw new AssertionError(e);
566 }
567 }
568
569
570 @Override
571 public int hashCode() {
572 return className.hashCode();
573 }
574
575
576
577
578
579
580
581
582 @Override
583 public boolean equals(Object o) {
584
585 if (o == null)
586 return false;
587
588
589 if (this == o)
590 return true;
591
592 if (!(o instanceof EntryRep))
593 return false;
594
595 EntryRep other = (EntryRep) o;
596
597 synchronized (this){
598
599 if (hash != other.hash)
600 return false;
601
602
603
604
605 if (values.length != other.values.length)
606 return false;
607
608
609
610
611
612
613
614
615
616
617 for (int i = 0; i < values.length; i++) {
618 if ((values[i] == null) && (other.values[i] != null))
619 return false;
620 if ((values[i] != null) && (other.values[i] == null))
621 return false;
622 }
623
624
625
626
627
628
629
630
631
632 for (int i = 0; i < values.length; i++) {
633
634 if (values[i] != null && !values[i].equals(other.values[i]))
635 return false;
636 }
637 }
638
639 return true;
640 }
641
642
643
644
645
646
647
648
649
650
651
652 static private boolean usableField(Field field) {
653
654 final int ignoreMods =
655 (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL);
656
657 if ((field.getModifiers() & ignoreMods) != 0)
658 return false;
659
660
661 if (field.getType().isPrimitive()) {
662 throw throwRuntime(new IllegalArgumentException(
663 "primitive field, " + field + ", not allowed in an Entry"));
664 }
665
666 return true;
667 }
668
669
670
671
672 public Uuid id() {
673 return id;
674 }
675
676
677
678
679
680
681 public void pickID() {
682 synchronized (this){
683 if (id != null)
684 throw new IllegalStateException("pickID called more than once");
685 id = UuidFactory.generate();
686 }
687 }
688
689
690
691
692 public MarshalledInstance value(int fieldNum) {
693 return values[fieldNum];
694 }
695
696
697
698
699 public int numFields() {
700 synchronized (this){
701 if (values != null) return values.length;
702 }
703 return 0;
704 }
705
706
707
708
709 public String classFor() {
710 return className;
711 }
712
713
714
715
716 public String[] superclasses() {
717 return superclasses != null ? superclasses.clone() : new String [0];
718 }
719
720
721
722
723 public long getHash() {
724 return hash;
725 }
726
727
728
729
730 public long[] getHashes() {
731 return hashes != null ? hashes.clone() : new long[0];
732 }
733
734
735
736
737
738
739
740
741 public boolean matches(EntryRep other) {
742
743
744
745
746
747
748
749
750
751
752 synchronized (this){
753 if (EntryRep.isMatchAny(this)) return true;
754
755 for (int f = 0; f < values.length; f++) {
756 if (values[f] == null) {
757 continue;
758 }
759 if (!values[f].equals(other.values[f])) {
760 return false;
761 }
762 }
763 }
764 return true;
765 }
766
767 @Override
768 public String toString() {
769 return ("EntryRep[" + className + "]");
770 }
771
772
773
774
775
776
777
778 public boolean isAtLeastA(String otherClass) {
779 if (otherClass.equals(matchAnyClassName()))
780
781 return true;
782 synchronized (this){
783 if (className.equals(otherClass))
784 return true;
785 for (int i = 0; i < superclasses.length; i++)
786 if (superclasses[i].equals(otherClass))
787 return true;
788 return false;
789 }
790 }
791
792
793 private static class FieldComparator implements Comparator {
794 public FieldComparator() {}
795
796
797 public int compare(Object o1, Object o2) {
798 Field f1 = (Field)o1;
799 Field f2 = (Field)o2;
800 if (f1 == f2)
801 return 0;
802 if (f1.getDeclaringClass() == f2.getDeclaringClass())
803 return f1.getName().compareTo(f2.getName());
804 if (f1.getDeclaringClass().isAssignableFrom(
805 f2.getDeclaringClass()))
806 return -1;
807 return 1;
808 }
809 }
810
811
812
813
814
815
816
817
818
819 private void readObject(ObjectInputStream in)
820 throws IOException, ClassNotFoundException
821 {
822 in.defaultReadObject();
823 if (className == null)
824 throw new InvalidObjectException("null className");
825
826 if (values == null)
827 throw new InvalidObjectException("null values");
828
829 if (superclasses == null)
830 throw new InvalidObjectException("null superclasses");
831
832 if (hashes == null)
833 throw new InvalidObjectException("null hashes");
834
835 if (hashes.length != superclasses.length)
836 throw new InvalidObjectException("hashes.length (" +
837 hashes.length + ") does not equal superclasses.length (" +
838 superclasses.length + ")");
839
840
841 integrity = MarshalledWrapper.integrityEnforced(in);
842 }
843
844
845
846
847
848
849 private void readObjectNoData() throws InvalidObjectException {
850 throw new
851 InvalidObjectException("SpaceProxy should always have data");
852 }
853
854
855
856
857
858 private void writeObject(ObjectOutputStream out) throws IOException {
859 out.defaultWriteObject();
860 }
861
862
863
864
865
866
867
868 public void setExpiration(long newExpiration) {
869 expires = newExpiration;
870 }
871
872
873 public long getExpiration() {
874 return expires;
875 }
876
877
878
879 public Uuid getCookie() {
880 return id;
881 }
882
883
884
885
886
887
888 public synchronized void store(ObjectOutputStream out) throws IOException {
889 final long bits0;
890 final long bits1;
891 if (id == null) {
892 bits0 = 0;
893 bits1 = 0;
894 } else {
895 bits0 = id.getMostSignificantBits();
896 bits1 = id.getLeastSignificantBits();
897 }
898 out.writeLong(bits0);
899 out.writeLong(bits1);
900 out.writeLong(expires);
901 out.writeObject(codebase);
902 out.writeObject(className);
903 out.writeObject(superclasses);
904 out.writeObject(values);
905 out.writeLong(hash);
906 out.writeObject(hashes);
907 }
908
909
910 public synchronized EntryRep restore(ObjectInputStream in)
911 throws IOException, ClassNotFoundException
912 {
913 final long bits0 = in.readLong();
914 final long bits1 = in.readLong();
915 if (bits0 == 0 && bits1 == 0) {
916 id = null;
917 } else {
918 id = UuidFactory.create(bits0, bits1);
919 }
920
921 expires = in.readLong();
922 codebase = (String)in.readObject();
923 className = (String)in.readObject();
924 superclasses = (String [])in.readObject();
925 values = (MarshalledInstance [])in.readObject();
926 hash = in.readLong();
927 hashes = (long[])in.readObject();
928 return this;
929 }
930
931
932
933 private static RuntimeException throwRuntime(RuntimeException e) {
934 if (logger.isLoggable(Levels.FAILED)) {
935 logger.log(Levels.FAILED, e.getMessage(), e);
936 }
937
938 throw e;
939 }
940
941
942 private static MarshalException throwNewMarshalException(
943 String msg, Exception nested)
944 throws MarshalException
945 {
946 final MarshalException me = new MarshalException(msg, nested);
947 if (logger.isLoggable(Levels.FAILED)) {
948 logger.log(Levels.FAILED, msg, me);
949 }
950
951 throw me;
952 }
953
954
955
956
957 private UnusableEntryException throwNewUnusableEntryException(
958 Entry partial, String[] badFields, Throwable[] exceptions)
959 throws UnusableEntryException
960 {
961 final UnusableEntryException uee =
962 new UnusableEntryException(partial, badFields, exceptions);
963
964 if (logger.isLoggable(Levels.FAILED)) {
965 logger.log(Levels.FAILED,
966 "failure constructing entry of type " + className, uee);
967 }
968
969 throw uee;
970 }
971
972
973
974
975
976 private static UnusableEntryException throwNewUnusableEntryException(
977 Throwable nested)
978 throws UnusableEntryException
979 {
980 final UnusableEntryException uee = new UnusableEntryException(nested);
981
982 if (logger.isLoggable(Levels.FAILED)) {
983 logger.log(Levels.FAILED, nested.getMessage(), uee);
984 }
985
986 throw uee;
987 }
988
989
990
991
992
993
994 private static UnusableEntryException throwNewUnusableEntryException(
995 String msg, Exception nested)
996 throws UnusableEntryException
997 {
998 final UnmarshalException ue = new UnmarshalException(msg, nested);
999 final UnusableEntryException uee = new UnusableEntryException(ue);
1000
1001 if (logger.isLoggable(Levels.FAILED)) {
1002 logger.log(Levels.FAILED, msg, uee);
1003 }
1004
1005 throw uee;
1006 }
1007 }