001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.invocation;
023:
024: import org.jboss.remoting.serialization.IMarshalledValue;
025: import org.jboss.remoting.serialization.SerializationStreamFactory;
026:
027: import java.io.DataOutputStream;
028: import java.io.ByteArrayOutputStream;
029: import java.io.IOException;
030: import java.lang.reflect.Method;
031: import java.security.DigestOutputStream;
032: import java.security.MessageDigest;
033: import java.security.Principal;
034: import java.security.PrivilegedAction;
035: import java.security.AccessController;
036: import java.util.Map;
037: import java.util.Iterator;
038: import java.util.HashMap;
039: import java.util.WeakHashMap;
040: import javax.transaction.Transaction;
041:
042: /**
043: * The MarshalledInvocation is an invocation that travels. As such it serializes
044: * its payload because of lack of ClassLoader visibility.
045: * As such it contains Marshalled data representing the byte[] of the Invocation object it extends
046: * Besides handling the specifics of "marshalling" the payload, which could be done at the Invocation level
047: * the Marshalled Invocation can hold optimization and needed code for distribution for example the
048: * TransactionPropagationContext which is a serialization of the TX for distribution purposes as
049: * well as the "hash" for the methods that we send, as opposed to sending Method objects.
050: * Serialization "optimizations" should be coded here in the externalization implementation of the class
051: *
052: * @author <a href="mailto:marc@jboss.org">Marc Fleury</a>
053: * @author Bill.Burke@jboss.org
054: * @author Scott.Stark@jboss.org
055: * @author Clebert.Suconic@jboss.org - added pluggable serialization
056: * @version $Revision: 57209 $
057: */
058: public class MarshalledInvocation extends Invocation implements
059: java.io.Externalizable {
060: // Constants -----------------------------------------------------
061:
062: static {
063: try {
064: Class
065: .forName("org.jboss.invocation.unified.interfaces.JavaSerializationManager");
066: } catch (Exception e) {
067: }
068: }
069:
070: /** Serial Version Identifier. */
071: static final long serialVersionUID = -718723094688127810L;
072: /** A flag indicating if the full hash format that includes the interface
073: * should be used
074: */
075: static boolean useFullHashMode = true;
076: /** WeakHashMap<Class, HashMap<String, Long>> of declaring class to hashes */
077: static Map hashMap = new WeakHashMap();
078:
079: /** The Transaction Propagation Context for distribution */
080: protected Object tpc;
081:
082: /** The Map of methods used by this Invocation */
083: protected transient Map methodMap;
084:
085: // These are here to avoid unneeded hash lookup
086: protected transient long methodHash = 0;
087: protected transient Object marshalledArgs = null;
088:
089: public long getMethodHash() {
090: return methodHash;
091: }
092:
093: public void setMethodHash(long methodHash) {
094: this .methodHash = methodHash;
095: }
096:
097: /** Get the full hash mode flag.
098: * @return the full hash mode flag.
099: */
100: public static boolean getUseFullHashMode() {
101: return useFullHashMode;
102: }
103:
104: /** Set the full hash mode flag. When true, method hashes are calculated
105: * using the getFullInterfaceHashes which is able to differentiate methods
106: * by declaring class, return value, name and arg signature, and exceptions.
107: * Otherwise, the getInterfaceHashes method uses, and this is only able to
108: * differentiate classes by return value, name and arg signature. A
109: * useFullHashMode = false is compatible with 3.2.3 and earlier.
110: *
111: * This needs to be set consistently on the server and the client.
112: *
113: * @param flag the full method hash calculation mode flag.
114: */
115: public static void setUseFullHashMode(boolean flag) {
116: useFullHashMode = flag;
117: }
118:
119: /** Calculate method hashes. This algo is taken from RMI with the
120: * method string built from the method name + parameters + return type. Note
121: * that this is not able to distinguish type compatible methods from
122: * different interfaces.
123: *
124: * @param intf - the class/interface to calculate method hashes for.
125: * @return Map<String, Long> mapping of method string to method desc hash
126: */
127: public static Map getInterfaceHashes(Class intf) {
128: // Create method hashes
129: Method[] methods = null;
130: if (System.getSecurityManager() != null) {
131: DeclaredMethodsAction action = new DeclaredMethodsAction(
132: intf);
133: methods = (Method[]) AccessController.doPrivileged(action);
134: } else {
135: methods = intf.getDeclaredMethods();
136: }
137:
138: HashMap map = new HashMap();
139: for (int i = 0; i < methods.length; i++) {
140: Method method = methods[i];
141: Class[] parameterTypes = method.getParameterTypes();
142: String methodDesc = method.getName() + "(";
143: for (int j = 0; j < parameterTypes.length; j++) {
144: methodDesc += getTypeString(parameterTypes[j]);
145: }
146: methodDesc += ")" + getTypeString(method.getReturnType());
147:
148: try {
149: long hash = 0;
150: ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(
151: 512);
152: MessageDigest messagedigest = MessageDigest
153: .getInstance("SHA");
154: DataOutputStream dataoutputstream = new DataOutputStream(
155: new DigestOutputStream(bytearrayoutputstream,
156: messagedigest));
157: dataoutputstream.writeUTF(methodDesc);
158: dataoutputstream.flush();
159: byte abyte0[] = messagedigest.digest();
160: for (int j = 0; j < Math.min(8, abyte0.length); j++)
161: hash += (long) (abyte0[j] & 0xff) << j * 8;
162: map.put(method.toString(), new Long(hash));
163: } catch (Exception e) {
164: e.printStackTrace();
165: }
166: }
167:
168: return map;
169: }
170:
171: /** Calculate method full hashes. This algo is taken from RMI with the full
172: * method string built from the method toString() which includes the
173: * modifiers, return type, declaring class, name, parameters and exceptions.
174: *
175: * @param intf - the class/interface to calculate method hashes for.
176: * @return Map<String, Long> mapping of method string to method desc hash
177: */
178: public static Map getFullInterfaceHashes(Class intf) {
179: // Create method hashes
180: Method[] methods = null;
181: if (System.getSecurityManager() != null) {
182: DeclaredMethodsAction action = new DeclaredMethodsAction(
183: intf);
184: methods = (Method[]) AccessController.doPrivileged(action);
185: } else {
186: methods = intf.getDeclaredMethods();
187: }
188:
189: HashMap map = new HashMap();
190: for (int i = 0; i < methods.length; i++) {
191: Method method = methods[i];
192: String methodDesc = method.toString();
193:
194: try {
195: long hash = 0;
196: ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(
197: 512);
198: MessageDigest messagedigest = MessageDigest
199: .getInstance("SHA");
200: DataOutputStream dataoutputstream = new DataOutputStream(
201: new DigestOutputStream(bytearrayoutputstream,
202: messagedigest));
203: dataoutputstream.writeUTF(methodDesc);
204: dataoutputstream.flush();
205: byte abyte0[] = messagedigest.digest();
206: for (int j = 0; j < Math.min(8, abyte0.length); j++)
207: hash += (long) (abyte0[j] & 0xff) << j * 8;
208: map.put(method.toString(), new Long(hash));
209: } catch (Exception e) {
210: e.printStackTrace();
211: }
212: }
213:
214: return map;
215: }
216:
217: /** Calculate method hashes. This algo is taken from RMI with the full
218: * method string taken from the method.toString to include the declaring
219: * class.
220: *
221: * @param c the class/interface to calculate method hashes for.
222: * @return Map<Long, Method> mapping of method hash to the Method object.
223: */
224: public static Map methodToHashesMap(Class c) {
225: // Create method hashes
226: Method[] methods = null;
227: if (System.getSecurityManager() != null) {
228: DeclaredMethodsAction action = new DeclaredMethodsAction(c);
229: methods = (Method[]) AccessController.doPrivileged(action);
230: } else {
231: methods = c.getDeclaredMethods();
232: }
233:
234: HashMap map = new HashMap();
235: for (int i = 0; i < methods.length; i++) {
236: Method method = methods[i];
237: String methodDesc = method.toString();
238:
239: try {
240: long hash = 0;
241: ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(
242: 512);
243: MessageDigest messagedigest = MessageDigest
244: .getInstance("SHA");
245: DataOutputStream dataoutputstream = new DataOutputStream(
246: new DigestOutputStream(bytearrayoutputstream,
247: messagedigest));
248: dataoutputstream.writeUTF(methodDesc);
249: dataoutputstream.flush();
250: byte abyte0[] = messagedigest.digest();
251: for (int j = 0; j < Math.min(8, abyte0.length); j++)
252: hash += (long) (abyte0[j] & 0xff) << j * 8;
253: map.put(new Long(hash), method);
254: } catch (Exception e) {
255: e.printStackTrace();
256: }
257: }
258:
259: return map;
260: }
261:
262: static String getTypeString(Class cl) {
263: if (cl == Byte.TYPE) {
264: return "B";
265: } else if (cl == Character.TYPE) {
266: return "C";
267: } else if (cl == Double.TYPE) {
268: return "D";
269: } else if (cl == Float.TYPE) {
270: return "F";
271: } else if (cl == Integer.TYPE) {
272: return "I";
273: } else if (cl == Long.TYPE) {
274: return "J";
275: } else if (cl == Short.TYPE) {
276: return "S";
277: } else if (cl == Boolean.TYPE) {
278: return "Z";
279: } else if (cl == Void.TYPE) {
280: return "V";
281: } else if (cl.isArray()) {
282: return "[" + getTypeString(cl.getComponentType());
283: } else {
284: return "L" + cl.getName().replace('.', '/') + ";";
285: }
286: }
287:
288: /*
289: * The use of hashCode is not enough to differenciate methods
290: * we override the hashCode
291: *
292: * The hashes are cached in a static for efficiency
293: */
294: public static long calculateHash(Method method) {
295: Map methodHashes = (Map) hashMap
296: .get(method.getDeclaringClass());
297:
298: if (methodHashes == null) {
299: // Add the method hashes for the class
300: if (useFullHashMode == true)
301: methodHashes = getFullInterfaceHashes(method
302: .getDeclaringClass());
303: else
304: methodHashes = getInterfaceHashes(method
305: .getDeclaringClass());
306: synchronized (hashMap) {
307: hashMap.put(method.getDeclaringClass(), methodHashes);
308: }
309: }
310:
311: Long hash = (Long) methodHashes.get(method.toString());
312: return hash.longValue();
313: }
314:
315: /** Remove all method hashes for the declaring class
316: * @param declaringClass a class for which a calculateHash(Method) was called
317: */
318: public static void removeHashes(Class declaringClass) {
319: synchronized (hashMap) {
320: hashMap.remove(declaringClass);
321: }
322: }
323:
324: // Constructors --------------------------------------------------
325: public MarshalledInvocation() {
326: // For externalization to work
327: }
328:
329: public MarshalledInvocation(Invocation invocation) {
330: this .payload = invocation.payload;
331: this .as_is_payload = invocation.as_is_payload;
332: this .method = invocation.getMethod();
333: this .objectName = invocation.getObjectName();
334: this .args = invocation.getArguments();
335: this .invocationType = invocation.getType();
336: this .transient_payload = invocation.transient_payload;
337: this .invocationContext = invocation.invocationContext;
338: }
339:
340: public MarshalledInvocation(Object id, Method m, Object[] args,
341: Transaction tx, Principal identity, Object credential) {
342: super (id, m, args, tx, identity, credential);
343: }
344:
345: public Method getMethod() {
346: if (this .method != null)
347: return this .method;
348:
349: // Try the hash, the methodMap should be set
350: this .method = (Method) methodMap.get(new Long(methodHash));
351:
352: // Keep it in the payload
353: if (this .method == null) {
354: throw new IllegalStateException(
355: "Failed to find method for hash:" + methodHash
356: + " available=" + methodMap);
357: }
358: return this .method;
359: }
360:
361: public void setMethodMap(Map methods) {
362: methodMap = methods;
363: }
364:
365: // The transaction propagation context for the Invocation that travels (distributed tx only)
366: public void setTransactionPropagationContext(Object tpc) {
367: this .tpc = tpc;
368: }
369:
370: public Object getTransactionPropagationContext() {
371: return tpc;
372: }
373:
374: // Invocation overwrite -----------------------------------------
375:
376: /** A Marshalled invocation has serialized data in the form of
377: MarshalledValue objects. We overwrite the "getValue" to deserialize the
378: data, this assume that the thread context class loader has visibility
379: on the classes.
380: */
381: public Object getValue(Object key) {
382:
383: Object value = super .getValue(key);
384:
385: // The map may contain serialized values of the fields
386: if (value instanceof IMarshalledValue) {
387: try {
388: IMarshalledValue mv = (IMarshalledValue) value;
389: value = mv.get();
390: } catch (Exception e) {
391: JBossLazyUnmarshallingException ise = new JBossLazyUnmarshallingException(
392: "getValue failed");
393: ise.initCause(e);
394: throw ise;
395: }
396: }
397: return value;
398: }
399:
400: /** A Marshalled invocation has serialized data in the form of
401: MarshalledValue objects. We overwrite the "getValue" to deserialize the
402: data, this assume that the thread context class loader has visibility
403: on the classes.
404: */
405: public Object getPayloadValue(Object key) {
406:
407: Object value = getPayload().get(key);
408:
409: // The map may contain serialized values of the fields
410: if (value instanceof MarshalledValue) {
411: try {
412: MarshalledValue mv = (MarshalledValue) value;
413: value = mv.get();
414: } catch (Exception e) {
415: JBossLazyUnmarshallingException ise = new JBossLazyUnmarshallingException(
416: "getPayloadValue failed");
417: ise.initCause(e);
418: throw ise;
419: }
420: } else if (value instanceof IMarshalledValue) {
421: try {
422: IMarshalledValue mv = (IMarshalledValue) value;
423: value = mv.get();
424: } catch (Exception e) {
425: JBossLazyUnmarshallingException ise = new JBossLazyUnmarshallingException(
426: "getPayloadValue failed");
427: ise.initCause(e);
428: throw ise;
429: }
430: }
431: return value;
432: }
433:
434: public Object[] getArguments() {
435: if (this .args == null) {
436: if (marshalledArgs instanceof MarshalledValue) {
437: try {
438: this .args = (Object[]) ((MarshalledValue) marshalledArgs)
439: .get();
440: } catch (Exception e) {
441: JBossLazyUnmarshallingException ise = new JBossLazyUnmarshallingException(
442: "getArguments failed");
443: ise.initCause(e);
444: throw ise;
445: }
446: } else if (marshalledArgs instanceof IMarshalledValue) {
447: try {
448: this .args = (Object[]) ((IMarshalledValue) marshalledArgs)
449: .get();
450: } catch (Exception e) {
451: JBossLazyUnmarshallingException ise = new JBossLazyUnmarshallingException(
452: "getArguments failed");
453: ise.initCause(e);
454: throw ise;
455: }
456: }
457: }
458: return args;
459: }
460:
461: // Externalizable implementation ---------------------------------
462: public void writeExternal(java.io.ObjectOutput out)
463: throws IOException {
464: // TODO invocationType should be removed from as is payload
465: // for now, it is in there for binary compatibility
466: getAsIsPayload().put(InvocationKey.TYPE, invocationType);
467: // FIXME marcf: the "specific" treatment of Transactions should be abstracted.
468: // Write the TPC, not the local transaction
469: out.writeObject(tpc);
470:
471: long methodHash = this .methodHash;
472: if (methodHash == 0) {
473: methodHash = calculateHash(this .method);
474: }
475:
476: out.writeLong(methodHash);
477:
478: out.writeObject(this .objectName);
479:
480: String serializationType = null;
481:
482: if (invocationContext != null) {
483: serializationType = (String) invocationContext
484: .getValue("SERIALIZATION_TYPE");
485: }
486:
487: if (this .args == null && this .marshalledArgs != null) {
488: out.writeObject(this .marshalledArgs);
489: } else {
490: out.writeObject(createMarshalledValue(serializationType,
491: this .args));
492: }
493:
494: // Write out payload hashmap
495: // Don't use hashmap serialization to avoid not-needed data being
496: // marshalled
497: // The map contains only serialized representations of every other object
498: // Everything else is possibly tied to classloaders that exist inside the
499: // server but not in the generic JMX land. they will travel in the payload
500: // as MarshalledValue objects, see the Invocation getter logic
501: //
502: if (payload == null)
503: out.writeInt(0);
504: else {
505: out.writeInt(payload.size());
506: Iterator keys = payload.keySet().iterator();
507: while (keys.hasNext()) {
508: Object currentKey = keys.next();
509:
510: // This code could be if (object.getClass().getName().startsWith("java")) then don't serialize.
511: // Bench the above for speed.
512:
513: out.writeObject(currentKey);
514: Object value = payload.get(currentKey);
515: // no reason to marshall an already marshalled value
516: if (!(value instanceof MarshalledValue)) {
517: value = createMarshalledValue(serializationType,
518: value);
519: }
520:
521: out.writeObject(value);
522: }
523: }
524:
525: // This map is "safe" as is
526: //out.writeObject(as_is_payload);
527: if (as_is_payload == null)
528: out.writeInt(0);
529: else {
530: out.writeInt(as_is_payload.size());
531:
532: Iterator keys = as_is_payload.keySet().iterator();
533: while (keys.hasNext()) {
534: Object currentKey = keys.next();
535: out.writeObject(currentKey);
536: out.writeObject(as_is_payload.get(currentKey));
537: }
538: }
539: }
540:
541: private Object createMarshalledValue(String serializationType,
542: Object valueToBeMarshalled) throws IOException {
543: if (serializationType != null) {
544: return (IMarshalledValue) SerializationStreamFactory
545: .getManagerInstance(serializationType)
546: .createdMarshalledValue(valueToBeMarshalled);
547: } else {
548: return new MarshalledValue(valueToBeMarshalled);
549: }
550: }
551:
552: public void readExternal(java.io.ObjectInput in)
553: throws IOException, ClassNotFoundException {
554: tpc = in.readObject();
555: this .methodHash = in.readLong();
556:
557: this .objectName = in.readObject();
558:
559: marshalledArgs = in.readObject();
560:
561: int payloadSize = in.readInt();
562: if (payloadSize > 0) {
563: payload = new HashMap();
564: for (int i = 0; i < payloadSize; i++) {
565: Object key = in.readObject();
566: Object value = in.readObject();
567: payload.put(key, value);
568: }
569: }
570:
571: int as_is_payloadSize = in.readInt();
572: if (as_is_payloadSize > 0) {
573: as_is_payload = new HashMap();
574: for (int i = 0; i < as_is_payloadSize; i++) {
575: Object key = in.readObject();
576: Object value = in.readObject();
577: as_is_payload.put(key, value);
578: }
579: }
580: // TODO invocationType should be removed from as is payload
581: // for now, it is in there for binary compatibility
582: invocationType = (InvocationType) getAsIsValue(InvocationKey.TYPE);
583: }
584:
585: /**
586: * This is method is used for chaing the MarshalledAruments in a Call-By-Value operation.
587: * */
588: public void setMarshalledArguments(IMarshalledValue marshalledValue) {
589: marshalledArgs = marshalledValue;
590: }
591:
592: private static class DeclaredMethodsAction implements
593: PrivilegedAction {
594: Class c;
595:
596: DeclaredMethodsAction(Class c) {
597: this .c = c;
598: }
599:
600: public Object run() {
601: Method[] methods = c.getDeclaredMethods();
602: c = null;
603: return methods;
604: }
605: }
606: }
|