001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.geronimo.corba.util;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.IOException;
022    import java.io.Serializable;
023    import java.io.UnsupportedEncodingException;
024    import java.io.ObjectOutputStream;
025    import java.rmi.Remote;
026    import java.rmi.UnexpectedException;
027    import java.rmi.RemoteException;
028    import java.lang.reflect.Method;
029    import java.util.List;
030    import java.util.LinkedList;
031    import java.util.Iterator;
032    import java.util.Map;
033    import java.util.Set;
034    import java.util.Arrays;
035    import java.util.LinkedHashSet;
036    import java.util.HashMap;
037    import java.util.HashSet;
038    import java.util.regex.Pattern;
039    import java.util.regex.Matcher;
040    
041    import javax.ejb.spi.HandleDelegate;
042    import javax.naming.InitialContext;
043    import javax.naming.NamingException;
044    import javax.rmi.PortableRemoteObject;
045    
046    import org.apache.geronimo.corba.ORBConfiguration;
047    import org.apache.geronimo.crypto.asn1.DERInputStream;
048    import org.apache.geronimo.crypto.asn1.DERObjectIdentifier;
049    import org.apache.geronimo.crypto.asn1.DEROutputStream;
050    import org.apache.geronimo.crypto.asn1.x509.GeneralName;
051    import org.apache.geronimo.crypto.asn1.x509.X509Name;
052    import org.omg.CORBA.Any;
053    import org.omg.CORBA.ORB;
054    import org.omg.CORBA.UserException;
055    import org.omg.CORBA.portable.ResponseHandler;
056    import org.omg.CORBA.portable.UnknownException;
057    import org.omg.CORBA.portable.IDLEntity;
058    import org.omg.GSSUP.GSSUPMechOID;
059    import org.omg.GSSUP.InitialContextToken;
060    import org.omg.GSSUP.InitialContextTokenHelper;
061    import org.omg.IOP.Codec;
062    import org.omg.IOP.CodecFactory;
063    import org.omg.IOP.ENCODING_CDR_ENCAPS;
064    import org.omg.IOP.Encoding;
065    import org.omg.CORBA_2_3.portable.OutputStream;
066    import org.omg.CORBA_2_3.portable.InputStream;
067    import org.apache.commons.logging.Log;
068    import org.apache.commons.logging.LogFactory;
069    import org.apache.geronimo.corba.CorbaApplicationServer;
070    import org.apache.openejb.ProxyInfo;
071    import org.apache.openejb.spi.ApplicationServer;
072    import org.apache.openejb.core.ServerFederation;
073    
074    /**
075     * Various utility functions.
076     * <p/>
077     * Note: #getORB() and #getCodec() rely on UtilInitializer to initialze the ORB and codec.
078     *
079     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
080     * @see UtilInitializer
081     */
082    public final class Util {
083        private static final Log log = LogFactory.getLog(Util.class);
084        private static final byte ASN_TAG_NT_EXPORTED_NAME1 = 0x04;
085        private static final byte ASN_TAG_NT_EXPORTED_NAME2 = 0x01;
086        private static final byte ASN_TAG_OID = 0x06;
087        private static final byte ASN_TAG_GSS = 0x60;
088        private static ORB orb;
089        private static Codec codec;
090        private static HandleDelegate handleDelegate;
091        private static CorbaApplicationServer corbaApplicationServer = new CorbaApplicationServer();
092        private static HashMap<String,ORBConfiguration> configuredOrbs = new HashMap<String,ORBConfiguration>(); 
093        
094        public static ORB getORB() {
095            assert orb != null;
096            return orb;
097        }
098        
099        public static void registerORB(String id, ORBConfiguration orb) {
100            configuredOrbs.put(id, orb); 
101        }
102        
103        public static ORBConfiguration getRegisteredORB(String id) {
104            return configuredOrbs.get(id); 
105        }
106        
107        public static void unregisterORB(String id) {
108            configuredOrbs.remove(id); 
109        }
110        
111    
112        public static void setORB(ORB orb) throws UserException {
113            if (Util.orb == null) {
114                Util.orb = orb;
115                CodecFactory factory = (CodecFactory) Util.orb.resolve_initial_references("CodecFactory");
116                codec = factory.create_codec(new Encoding(ENCODING_CDR_ENCAPS.value, (byte) 1, (byte) 2));
117            }
118        }
119    
120        public static Codec getCodec() {
121            assert codec != null;
122            return codec;
123        }
124    
125        public static HandleDelegate getHandleDelegate() throws NamingException {
126            if (handleDelegate == null) {
127                InitialContext ic = new InitialContext();
128                handleDelegate = (HandleDelegate) ic.lookup("java:comp/HandleDelegate");
129            }
130            return handleDelegate;
131        }
132        
133        public static Object getEJBProxy(ProxyInfo info) {
134            if (info.getInterfaceType().isHome()) {
135                return corbaApplicationServer.getEJBHome(info); 
136            }
137            else {
138                return corbaApplicationServer.getEJBObject(info); 
139            }
140        }
141    
142        public static byte[] encodeOID(String oid) throws IOException {
143            oid = (oid.startsWith("oid:") ? oid.substring(4) : oid);
144            return encodeOID(new DERObjectIdentifier(oid));
145        }
146    
147        public static byte[] encodeOID(DERObjectIdentifier oid) throws IOException {
148            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
149            DEROutputStream dOut = new DEROutputStream(bOut);
150    
151            dOut.writeObject(oid);
152    
153            return bOut.toByteArray();
154        }
155    
156        public static String decodeOID(byte[] oid) throws IOException {
157            return decodeOIDDERObjectIdentifier(oid).getId();
158        }
159    
160        public static DERObjectIdentifier decodeOIDDERObjectIdentifier(byte[] oid) throws IOException {
161            ByteArrayInputStream bIn = new ByteArrayInputStream(oid);
162            DERInputStream dIn = new DERInputStream(bIn);
163    
164            return (DERObjectIdentifier) dIn.readObject();
165        }
166    
167        public static byte[] encodeGeneralName(String name) throws IOException {
168            return encodeGeneralName(new X509Name(name));
169        }
170    
171        public static byte[] encodeGeneralName(X509Name x509Name) throws IOException {
172            return encodeGeneralName(new GeneralName(x509Name));
173        }
174    
175        public static byte[] encodeGeneralName(GeneralName generalName) throws IOException {
176            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
177            DEROutputStream dOut = new DEROutputStream(bOut);
178    
179            dOut.writeObject(generalName);
180    
181            return bOut.toByteArray();
182        }
183    
184        public static String decodeGeneralName(byte[] name) throws IOException {
185            throw new java.lang.UnsupportedOperationException();
186        }
187    
188        /**
189         * This method encodes a name as if it was encoded using the GSS-API
190         * gss_export_name() function call (see RFC 2743, page 84).
191         * The oid to indicate names of this format is:<br/>
192         * {1(iso), 3(org), 6(dod), 1(internet), 5(security), 6(nametypes),
193         * 4(gss-api-exported-name)}<br/>
194         * The token has the following format:
195         * <table>
196         * <tr><td><b>Offset</b></td><td><b>Meaning</b></td><td><b>Value</b></td></tr>
197         * <tr><td>0</td><td>token id</td><td>0x04</td></tr>
198         * <tr><td>1</td><td>token id</td><td>0x01</td></tr>
199         * <p/>
200         * <tr><td>2</td><td>oid length</td><td>hi-byte (len/0xFF)</td></tr>
201         * <tr><td>3</td><td>oid length</td><td>lo-byte (len%0xFF)</td></tr>
202         * <p/>
203         * <tr><td>4</td><td>oid</td><td>oid:1.3.6.1.5.6.4</td></tr>
204         * <p/>
205         * <tr><td>n+0</td><td>name length</td><td>len/0xFFFFFF</td></tr>
206         * <tr><td>n+1</td><td>name length</td><td>(len%0xFFFFFF)/0xFFFF</td></tr>
207         * <tr><td>n+2</td><td>name length</td><td>((len%0xFFFFFF)%0xFFFF)/0xFF</td></tr>
208         * <tr><td>n+3</td><td>name length</td><td>((len%0xFFFFFF)%0xFFFF)%0xFF</td></tr>
209         * <p/>
210         * <tr><td>n+4</td><td>name</td><td>foo</td></tr>
211         * </table>
212         *
213         * @param oid  The oid of the mechanism this name is exported from.
214         * @param name The name to be exported.
215         * @return The byte array representing the exported name object.
216         */
217        public static byte[] encodeGSSExportName(String oid, String name) {
218            try {
219                byte[] oid_arr = encodeOID(oid);
220                int oid_len = oid_arr.length;
221                byte[] name_arr = name.getBytes("UTF-8");
222                int name_len = name_arr.length;
223    
224                ByteArrayOutputStream baos = new ByteArrayOutputStream();
225                // token id at 0
226                baos.write(ASN_TAG_NT_EXPORTED_NAME1);
227                baos.write(ASN_TAG_NT_EXPORTED_NAME2);
228    
229                // write the two length bytes
230                baos.write((byte) (oid_len & 0xFF00) >> 8);
231                baos.write((byte) (oid_len & 0x00FF));
232    
233                // oid at 2
234                baos.write(oid_arr);
235    
236                // name length at n
237                baos.write((byte) (name_len & 0xFF000000) >> 24);
238                baos.write((byte) (name_len & 0x00FF0000) >> 16);
239                baos.write((byte) (name_len & 0x0000FF00) >> 8);
240                baos.write((byte) (name_len & 0x000000FF));
241    
242                // name at n+4
243                baos.write(name_arr);
244                return baos.toByteArray();
245            } catch (Exception ex) {
246                // do nothing, return null
247            }
248            return null;
249        }
250    
251        /**
252         * This function reads a name from a byte array which was created
253         * by the gssExportName() method.
254         *
255         * @param name_tok The GSS name token.
256         * @return The name from the GSS name token.
257         */
258        public static String decodeGSSExportName(byte[] name_tok) {
259            String result = null;
260            if (name_tok != null) {
261                ByteArrayInputStream bais = new ByteArrayInputStream(name_tok);
262                try {
263                    // GSSToken tag 1 0x04
264                    int t1 = bais.read();
265                    if (t1 == ASN_TAG_NT_EXPORTED_NAME1) {
266                        // GSSToken tag 2 0x01
267                        int t2 = bais.read();
268                        if (t2 == ASN_TAG_NT_EXPORTED_NAME2) {
269                            // read the two length bytes
270                            int l = bais.read() << 8;
271                            l += bais.read();
272    
273                            // read the oid
274                            byte[] oid_arr = new byte[l];
275                            bais.read(oid_arr, 0, l);
276                            String oid = decodeOID(oid_arr);
277    
278                            if (oid.equals(GSSUPMechOID.value.substring(4))) {
279                                int l1 = bais.read();
280                                int l2 = bais.read();
281                                int l3 = bais.read();
282                                int l4 = bais.read();
283    
284                                int name_len = (l1 << 24) + (l2 << 16) + (l3 << 8) + l4;
285                                byte[] name_arr = new byte[name_len];
286                                bais.read(name_arr, 0, name_len);
287                                result = new String(name_arr);
288                            } else {
289                                System.err.print("ASN1Utils.gssImportName: Unknown OID: " + oid +
290                                        " ('" + Integer.toHexString(oid_arr[0]) + "')");
291                            }
292                        }
293                    }
294                } catch (Exception ex) {
295                    ex.printStackTrace();
296                    // do nothing, return null
297                }
298            }
299            return result;
300        }
301    
302        private static final Pattern SCOPED_NAME_EXTRACTION_PATTERN = Pattern.compile("(\\\\\\\\)|(\\\\@)|(@)|(\\z)");
303    
304        /**
305         * See csiv2 spec 16.2.5 par. 63-64.  We extract the username if any and un-escape any
306         * escaped \ and @ characters.
307         * 
308         * @param scopedNameBytes
309         * @return
310         * @throws UnsupportedEncodingException
311         */
312        public static String extractUserNameFromScopedName(byte[] scopedNameBytes) throws UnsupportedEncodingException {
313            String scopedUserName = new String(scopedNameBytes, "UTF8");
314            return extractUserNameFromScopedName(scopedUserName);
315        }
316    
317        public static String extractUserNameFromScopedName(String scopedUserName) {
318            Matcher m = SCOPED_NAME_EXTRACTION_PATTERN.matcher(scopedUserName);
319            StringBuffer buf = new StringBuffer();
320            while (m.find()) {
321                m.appendReplacement(buf, "");
322                if (m.group(1) != null) {
323                    buf.append('\\');
324                } else if (m.group(2) != null) {
325                    buf.append("@");
326                } else if (m.group(3) != null) {
327                    break;
328                }
329            }
330            return buf.toString();
331        }
332    
333        private static final Pattern SCOPED_NAME_ESCAPE_PATTERN = Pattern.compile("(\\\\)|(@)");
334    
335        public static String buildScopedUserName(String user, String domain) {
336            StringBuffer buf = new StringBuffer();
337            if (user != null) {
338                escape(user, buf);
339            }
340            if (domain != null) {
341                buf.append('@');
342                escape(domain, buf);
343            }
344            return buf.toString();
345        }
346    
347        private static void escape(String s, StringBuffer buf) {
348            Matcher m = SCOPED_NAME_ESCAPE_PATTERN.matcher(s);
349            while (m.find()) {
350                m.appendReplacement(buf, "");
351                if (m.group(1) != null) {
352                    buf.append("\\\\");
353                } else if (m.group(2) != null) {
354                    buf.append("\\@");
355                }
356            }
357            m.appendTail(buf);
358        }
359    
360    
361        /**
362         * Encode a mechanism independent initial context token (GSSToken). Defined
363         * in [IETF RFC 2743] Section 3.1, "Mechanism-Independent token Format" pp. 81-82.
364         * <table>
365         * <tr><td><b>Offset</b></td><td><b>Meaning</b></td></tr>
366         * <tr><td>0</td><td>ASN1 tag</td></tr>
367         * <tr><td>1</td><td>token length (&lt;128)</td></tr>
368         * <tr><td>2</td><td>mechanism oid</td></tr>
369         * <tr><td>n</td><td>mechanism specific token (e.g. GSSUP::InitialContextToken)</td></tr>
370         * </table>
371         * Currently only one mechanism specific token is supported: GSS username password
372         * (GSSUP::InitialContextToken).
373         *
374         * @param orb    The orb to get an Any from.
375         * @param codec  The codec to do the encoding of the Any.
376         * @param user   The username.
377         * @param pwd    The password of the user.
378         * @param target The target name.
379         * @return The byte array of the ASN1 encoded GSSToken.
380         */
381        public static byte[] encodeGSSUPToken(ORB orb, Codec codec, String user, String pwd, String target) {
382            byte[] result = null;
383            try {
384                // write the GSS ASN tag
385                ByteArrayOutputStream baos = new ByteArrayOutputStream();
386                baos.write(ASN_TAG_GSS);
387    
388                // create and encode a GSSUP initial context token
389                InitialContextToken init_token = new InitialContextToken();
390                init_token.username = user.getBytes("UTF-8");
391    
392                init_token.password = pwd.getBytes("UTF-8");
393    
394                init_token.target_name = encodeGSSExportName(GSSUPMechOID.value.substring(4), target);
395    
396                Any a = orb.create_any();
397                InitialContextTokenHelper.insert(a, init_token);
398                byte[] init_ctx_token = codec.encode_value(a);
399    
400                // encode the mechanism oid
401                byte[] oid_arr = encodeOID(GSSUPMechOID.value.substring(4));
402    
403                // write the length
404                baos.write((byte) (oid_arr.length + init_ctx_token.length + 2));
405    
406                // write the mechanism oid
407                baos.write(oid_arr);
408    
409                // write the
410                baos.write(init_ctx_token);
411    
412                // get the bytes
413                result = baos.toByteArray();
414            } catch (Exception ex) {
415                // do nothing, return null
416            }
417            return result;
418        }
419    
420        /**
421         * Decode an GSSUP InitialContextToken from a GSSToken.
422         *
423         * @param codec     The codec to do the encoding of the Any.
424         * @param gssup_tok The InitialContextToken struct to fill in the decoded values.
425         * @return Return true when decoding was successful, false otherwise.
426         */
427        public static boolean decodeGSSUPToken(Codec codec, byte[] token_arr,
428                                               InitialContextToken gssup_tok) {
429            boolean result = false;
430            if (gssup_tok != null) {
431                ByteArrayInputStream bais = new ByteArrayInputStream(token_arr);
432                try {
433                    // GSSToken tag
434                    int c = bais.read();
435                    if (c == ASN_TAG_GSS) {
436                        // GSSToken length
437                        int token_len = bais.read();
438                        // OID tag
439                        int oid_tag = bais.read();
440                        if (oid_tag == ASN_TAG_OID) {
441                            // OID length
442                            int oid_len = bais.read();
443                            byte[] oid_tmp_arr = new byte[oid_len];
444                            bais.read(oid_tmp_arr, 0, oid_len);
445                            byte[] oid_arr = new byte[oid_len + 2];
446                            oid_arr[0] = (byte) oid_tag;
447                            oid_arr[1] = (byte) oid_len;
448                            System.arraycopy(oid_tmp_arr, 0, oid_arr, 2, oid_len);
449                            String oid = decodeOID(oid_arr);
450                            if (oid.equals(GSSUPMechOID.value.substring(4))) {
451                                int len = token_len - oid_len;
452                                byte[] init_tok_arr = new byte[len];
453                                bais.read(init_tok_arr, 0, len);
454                                Any a = codec.decode_value(init_tok_arr,
455                                        InitialContextTokenHelper.type());
456                                InitialContextToken token = InitialContextTokenHelper.extract(a);
457                                if (token != null) {
458                                    gssup_tok.username = token.username;
459                                    gssup_tok.password = token.password;
460                                    gssup_tok.target_name = decodeGSSExportName(token.target_name).getBytes("UTF-8");
461    
462                                    result = true;
463                                }
464                            }
465                        }
466                    }
467                } catch (Exception ex) {
468                    // do nothing, return false
469                }
470            }
471            return result;
472        }
473    
474        public static String byteToString(byte[] data) {
475            StringBuffer buffer = new StringBuffer();
476            for (int i = 0; i < data.length; i++) {
477                buffer.append(HEXCHAR[(data[i] >>> 4) & 0x0F]);
478                buffer.append(HEXCHAR[(data[i]) & 0x0F]);
479            }
480            return buffer.toString();
481    
482        }
483    
484        private static final char[] HEXCHAR = {
485            '0', '1', '2', '3', '4', '5', '6', '7',
486            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
487        };
488    
489        public static void writeObject(Class type, Object object, OutputStream out) {
490            if (type == Void.TYPE) {
491                // do nothing for a void
492            } else if (type == Boolean.TYPE) {
493                out.write_boolean(((Boolean) object).booleanValue());
494            } else if (type == Byte.TYPE) {
495                out.write_octet(((Byte) object).byteValue());
496            } else if (type == Character.TYPE) {
497                out.write_wchar(((Character) object).charValue());
498            } else if (type == Double.TYPE) {
499                out.write_double(((Double) object).doubleValue());
500            } else if (type == Float.TYPE) {
501                out.write_float(((Float) object).floatValue());
502            } else if (type == Integer.TYPE) {
503                out.write_long(((Integer) object).intValue());
504            } else if (type == Long.TYPE) {
505                out.write_longlong(((Long) object).longValue());
506            } else if (type == Short.TYPE) {
507                out.write_short(((Short) object).shortValue());
508            }  else {
509                // object types must bbe written in the context of the corba application server
510                // which properly write replaces our objects for corba
511                ApplicationServer oldApplicationServer = ServerFederation.getApplicationServer();
512                try {
513                    ServerFederation.setApplicationServer(corbaApplicationServer);
514    
515                    // todo check if
516                    // copy the result to force replacement
517                    // corba does not call writeReplace on remote proxies
518                    //
519                    // HOWEVER, if this is an array, then we don't want to do the replacement 
520                    // because we can end up with a replacement element that's not compatible with the 
521                    // original array type, which results in an ArrayStoreException.  Fortunately, 
522                    // the Yoko RMI support appears to be able to sort this out for us correctly. 
523                    if (object instanceof Serializable && !object.getClass().isArray()) {
524                        try {
525                            object = copyObj(Thread.currentThread().getContextClassLoader(), object);
526                        } catch (Exception e) {
527                            log.debug("Exception in result copy", e);
528                            throw new UnknownException(e);
529                        }
530                    }
531    
532                    if (type == Object.class || type == Serializable.class) {
533                        javax.rmi.CORBA.Util.writeAny(out, object);
534                    } else if (org.omg.CORBA.Object.class.isAssignableFrom(type)) {
535                        out.write_Object((org.omg.CORBA.Object) object);
536                    } else if (Remote.class.isAssignableFrom(type)) {
537                        javax.rmi.CORBA.Util.writeRemoteObject(out, object);
538                    } else if (type.isInterface() && Serializable.class.isAssignableFrom(type)) {
539                        javax.rmi.CORBA.Util.writeAbstractObject(out, object);
540                    } else {
541                        out.write_value((Serializable) object, type);
542                    }
543                } finally {
544                    ServerFederation.setApplicationServer(oldApplicationServer);
545                }
546            }
547        }
548    
549        private static Object copyObj(ClassLoader classLoader, Object object) throws IOException, ClassNotFoundException {
550            ByteArrayOutputStream baos = new ByteArrayOutputStream();
551            ObjectOutputStream oos = new ObjectOutputStream(baos);
552            oos.writeObject(object);
553            oos.flush();
554            oos.close();
555            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
556            ObjectInputStreamExt ois = new ObjectInputStreamExt(bais, classLoader);
557            return ois.readObject();
558        }
559    
560        public static Object readObject(Class type, InputStream in) {
561            if (type == Void.TYPE) {
562                return null;
563            } else if (type == Boolean.TYPE) {
564                return new Boolean(in.read_boolean());
565            } else if (type == Byte.TYPE) {
566                return new Byte(in.read_octet());
567            } else if (type == Character.TYPE) {
568                return new Character(in.read_wchar());
569            } else if (type == Double.TYPE) {
570                return new Double(in.read_double());
571            } else if (type == Float.TYPE) {
572                return new Float(in.read_float());
573            } else if (type == Integer.TYPE) {
574                return new Integer(in.read_long());
575            } else if (type == Long.TYPE) {
576                return new Long(in.read_longlong());
577            } else if (type == Short.TYPE) {
578                return new Short(in.read_short());
579            } else if (type == Object.class || type == Serializable.class) {
580                return javax.rmi.CORBA.Util.readAny(in);
581            } else if (org.omg.CORBA.Object.class.isAssignableFrom(type)) {
582                return in.read_Object(type);
583            } else if (Remote.class.isAssignableFrom(type)) {
584                return PortableRemoteObject.narrow(in.read_Object(), type);
585            } else if (type.isInterface() && Serializable.class.isAssignableFrom(type)) {
586                return in.read_abstract_interface();
587            } else {
588                return in.read_value(type);
589            }
590        }
591    
592        public static void throwException(Method method, InputStream in) throws Throwable {
593            // read the exception id
594            final String id = in.read_string();
595    
596            // get the class name from the id
597            if (!id.startsWith("IDL:")) {
598                log.warn("Malformed exception id: " + id);
599                return;
600            }
601    
602            Class[] exceptionTypes = method.getExceptionTypes();
603            for (int i = 0; i < exceptionTypes.length; i++) {
604                Class exceptionType = exceptionTypes[i];
605    
606                String exceptionId = getExceptionId(exceptionType);
607                if (id.equals(exceptionId)) {
608                    throw (Throwable) in.read_value(exceptionType);
609                }
610            }
611            throw new UnexpectedException(id);
612        }
613    
614        public static OutputStream writeUserException(Method method, ResponseHandler reply, Exception exception) throws Exception {
615            if (exception instanceof RuntimeException || exception instanceof RemoteException) {
616                throw exception;
617            }
618    
619            Class[] exceptionTypes = method.getExceptionTypes();
620            for (int i = 0; i < exceptionTypes.length; i++) {
621                Class exceptionType = exceptionTypes[i];
622                if (!exceptionType.isInstance(exception)) {
623                    continue;
624                }
625    
626                OutputStream out = (OutputStream) reply.createExceptionReply();
627                String exceptionId = getExceptionId(exceptionType);
628                out.write_string(exceptionId);
629                out.write_value(exception);
630                return out;
631            }
632            throw exception;
633        }
634    
635        private static String getExceptionId(Class exceptionType) {
636            String exceptionName = exceptionType.getName().replace('.', '/');
637            if (exceptionName.endsWith("Exception")) {
638                exceptionName = exceptionName.substring(0, exceptionName.length() - "Exception".length());
639            }
640            exceptionName += "Ex";
641            String exceptionId = "IDL:" + exceptionName + ":1.0";
642            return exceptionId;
643        }
644    
645        public static String[] createCorbaIds(Class type) {
646            List ids = new LinkedList();
647            for (Iterator iterator = getAllInterfaces(type).iterator(); iterator.hasNext();) {
648                Class superInterface = (Class) iterator.next();
649                if (Remote.class.isAssignableFrom(superInterface) && superInterface != Remote.class) {
650                    ids.add("RMI:" + superInterface.getName() + ":0000000000000000");
651                }
652            }
653            return (String[]) ids.toArray(new String[ids.size()]);
654        }
655    
656        private static Set getAllInterfaces(Class intfClass) {
657            Set allInterfaces = new LinkedHashSet();
658    
659            LinkedList stack = new LinkedList();
660            stack.addFirst(intfClass);
661    
662            while (!stack.isEmpty()) {
663                Class intf = (Class) stack.removeFirst();
664                allInterfaces.add(intf);
665                stack.addAll(0, Arrays.asList(intf.getInterfaces()));
666            }
667    
668            return allInterfaces;
669        }
670    
671        public static Map mapMethodToOperation(Class intfClass) {
672            return iiopMap(intfClass, false);
673        }
674    
675        public static Map mapOperationToMethod(Class intfClass) {
676            return iiopMap(intfClass, true);
677        }
678    
679        private static Map iiopMap(Class intfClass, boolean operationToMethod) {
680            Method[] methods = getAllMethods(intfClass);
681    
682            // find every valid getter
683            HashMap getterByMethod = new HashMap(methods.length);
684            HashMap getterByName = new HashMap(methods.length);
685            for (int i = 0; i < methods.length; i++) {
686                Method method = methods[i];
687                String methodName = method.getName();
688    
689                // no arguments allowed
690                if (method.getParameterTypes().length != 0) {
691                    continue;
692                }
693    
694                // must start with get or is
695                String verb;
696                if (methodName.startsWith("get") && methodName.length() > 3 && method.getReturnType() != void.class) {
697                    verb = "get";
698                } else if (methodName.startsWith("is") && methodName.length() > 2 && method.getReturnType() == boolean.class) {
699                    verb = "is";
700                } else {
701                    continue;
702                }
703    
704                // must only throw Remote or Runtime Exceptions
705                boolean exceptionsValid = true;
706                Class[] exceptionTypes = method.getExceptionTypes();
707                for (int j = 0; j < exceptionTypes.length; j++) {
708                    Class exceptionType = exceptionTypes[j];
709                    if (!RemoteException.class.isAssignableFrom(exceptionType) &&
710                            !RuntimeException.class.isAssignableFrom(exceptionType) &&
711                            !Error.class.isAssignableFrom(exceptionType)) {
712                        exceptionsValid = false;
713                        break;
714                    }
715                }
716                if (!exceptionsValid) {
717                    continue;
718                }
719    
720                String propertyName;
721                if (methodName.length() > verb.length() + 1 && Character.isUpperCase(methodName.charAt(verb.length() + 1))) {
722                    propertyName = methodName.substring(verb.length());
723                } else {
724                    propertyName = Character.toLowerCase(methodName.charAt(verb.length())) + methodName.substring(verb.length() + 1);
725                }
726                getterByMethod.put(method, propertyName);
727                getterByName.put(propertyName, method);
728            }
729    
730            HashMap setterByMethod = new HashMap(methods.length);
731            for (int i = 0; i < methods.length; i++) {
732                Method method = methods[i];
733                String methodName = method.getName();
734    
735                // must have exactally one arg
736                if (method.getParameterTypes().length != 1) {
737                    continue;
738                }
739    
740                // must return non void
741                if (method.getReturnType() != void.class) {
742                    continue;
743                }
744    
745                // must start with set
746                if (!methodName.startsWith("set") || methodName.length() <= 3) {
747                    continue;
748                }
749    
750                // must only throw Remote or Runtime Exceptions
751                boolean exceptionsValid = true;
752                Class[] exceptionTypes = method.getExceptionTypes();
753                for (int j = 0; j < exceptionTypes.length; j++) {
754                    Class exceptionType = exceptionTypes[j];
755                    if (!RemoteException.class.isAssignableFrom(exceptionType) &&
756                            !RuntimeException.class.isAssignableFrom(exceptionType) &&
757                            !Error.class.isAssignableFrom(exceptionType)) {
758                        exceptionsValid = false;
759                        break;
760                    }
761                }
762                if (!exceptionsValid) {
763                    continue;
764                }
765    
766                String propertyName;
767                if (methodName.length() > 4 && Character.isUpperCase(methodName.charAt(4))) {
768                    propertyName = methodName.substring(3);
769                } else {
770                    propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
771                }
772    
773                // must have a matching getter
774                Method getter = (Method) getterByName.get(propertyName);
775                if (getter == null) {
776                    continue;
777                }
778    
779                // setter property must match gettter return value
780                if (!method.getParameterTypes()[0].equals(getter.getReturnType())) {
781                    continue;
782                }
783                setterByMethod.put(method, propertyName);
784            }
785    
786            // index the methods by name... used to determine which methods are overloaded
787            HashMap overloadedMethods = new HashMap(methods.length);
788            for (int i = 0; i < methods.length; i++) {
789                Method method = methods[i];
790                if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) {
791                    continue;
792                }
793                String methodName = method.getName();
794                List methodList = (List) overloadedMethods.get(methodName);
795                if (methodList == null) {
796                    methodList = new LinkedList();
797                    overloadedMethods.put(methodName, methodList);
798                }
799                methodList.add(method);
800            }
801    
802            // index the methods by lower case name... used to determine which methods differ only by case
803            HashMap caseCollisionMethods = new HashMap(methods.length);
804            for (int i = 0; i < methods.length; i++) {
805                Method method = methods[i];
806                if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) {
807                    continue;
808                }
809                String lowerCaseMethodName = method.getName().toLowerCase();
810                Set methodSet = (Set) caseCollisionMethods.get(lowerCaseMethodName);
811                if (methodSet == null) {
812                    methodSet = new HashSet();
813                    caseCollisionMethods.put(lowerCaseMethodName, methodSet);
814                }
815                methodSet.add(method.getName());
816            }
817    
818            String className = getClassName(intfClass);
819            Map iiopMap = new HashMap(methods.length);
820            for (int i = 0; i < methods.length; i++) {
821                Method method = methods[i];
822    
823                String iiopName = (String) getterByMethod.get(method);
824                if (iiopName != null) {
825                    // if we have a leading underscore prepend with J
826                    if (iiopName.charAt(0) == '_') {
827                        iiopName = "J_get_" + iiopName.substring(1);
828                    } else {
829                        iiopName = "_get_" + iiopName;
830                    }
831                } else {
832                    iiopName = (String) setterByMethod.get(method);
833                    if (iiopName != null) {
834                        // if we have a leading underscore prepend with J
835                        if (iiopName.charAt(0) == '_') {
836                            iiopName = "J_set_" + iiopName.substring(1);
837                        } else {
838                            iiopName = "_set_" + iiopName;
839                        }
840                    } else {
841                        iiopName = method.getName();
842    
843                        // if we have a leading underscore prepend with J
844                        if (iiopName.charAt(0) == '_') {
845                            iiopName = "J" + iiopName;
846                        }
847                    }
848                }
849    
850                // if this name only differs by case add the case index to the end
851                Set caseCollisions = (Set) caseCollisionMethods.get(method.getName().toLowerCase());
852                if (caseCollisions != null && caseCollisions.size() > 1) {
853                    iiopName += upperCaseIndexString(iiopName);
854                }
855    
856                // if this is an overloaded method append the parameter string
857                List overloads = (List) overloadedMethods.get(method.getName());
858                if (overloads != null && overloads.size() > 1) {
859                    iiopName += buildOverloadParameterString(method.getParameterTypes());
860                }
861    
862                // if we have a leading underscore prepend with J
863                iiopName = replace(iiopName, '$', "U0024");
864    
865                // if we have matched a keyword prepend with an underscore
866                if (keywords.contains(iiopName.toLowerCase())) {
867                    iiopName = "_" + iiopName;
868                }
869    
870                // if the name is the same as the class name, append an underscore
871                if (iiopName.equalsIgnoreCase(className)) {
872                    iiopName += "_";
873                }
874    
875                if (operationToMethod) {
876                    iiopMap.put(iiopName, method);
877                } else {
878                    iiopMap.put(method, iiopName);
879                }
880            }
881    
882            return iiopMap;
883        }
884    
885        private static Method[] getAllMethods(Class intfClass) {
886            LinkedList methods = new LinkedList();
887            for (Iterator iterator = getAllInterfaces(intfClass).iterator(); iterator.hasNext();) {
888                Class intf = (Class) iterator.next();
889                methods.addAll(Arrays.asList(intf.getDeclaredMethods()));
890            }
891    
892            return (Method[]) methods.toArray(new Method[methods.size()]);
893        }
894    
895        /**
896         * Return the a string containing an underscore '_' index of each uppercase character in the iiop name.
897         *
898         * This is used for distinction of names that only differ by case, since corba does not support case sensitive names.
899         */
900        private static String upperCaseIndexString(String iiopName) {
901            StringBuffer stringBuffer = new StringBuffer();
902            for (int i = 0; i < iiopName.length(); i++) {
903                char c = iiopName.charAt(i);
904                if (Character.isUpperCase(c)) {
905                    stringBuffer.append('_').append(i);
906                }
907            }
908            return stringBuffer.toString();
909        }
910    
911        /**
912         * Replaces any occurnace of the specified "oldChar" with the nes string.
913         *
914         * This is used to replace occurances if '$' in corba names since '$' is a special character
915         */
916        private static String replace(String source, char oldChar, String newString) {
917            StringBuffer stringBuffer = new StringBuffer(source.length());
918            for (int i = 0; i < source.length(); i++) {
919                char c = source.charAt(i);
920                if (c == oldChar) {
921                    stringBuffer.append(newString);
922                } else {
923                    stringBuffer.append(c);
924                }
925            }
926            return stringBuffer.toString();
927        }
928    
929        /**
930         * Return the a string containing a double underscore '__' list of parameter types encoded using the Java to IDL rules.
931         *
932         * This is used for distinction of methods that only differ by parameter lists.
933         */
934        private static String buildOverloadParameterString(Class[] parameterTypes) {
935            String name = "";
936            if (parameterTypes.length ==0) {
937                name += "__";
938            } else {
939                for (int i = 0; i < parameterTypes.length; i++) {
940                    Class parameterType = parameterTypes[i];
941                    name += buildOverloadParameterString(parameterType);
942                }
943            }
944            return name.replace('.', '_');
945        }
946    
947        /**
948         * Returns a single parameter type encoded using the Java to IDL rules.
949         */
950        private static String buildOverloadParameterString(Class parameterType) {
951            String name = "_";
952    
953            int arrayDimensions = 0;
954            while (parameterType.isArray()) {
955                arrayDimensions++;
956                parameterType = parameterType.getComponentType();
957            }
958    
959            // arrays start with org_omg_boxedRMI_
960            if (arrayDimensions > 0) {
961                name += "_org_omg_boxedRMI";
962            }
963    
964            // IDLEntity types must be prefixed with org_omg_boxedIDL_
965            if (IDLEntity.class.isAssignableFrom(parameterType)) {
966                name += "_org_omg_boxedIDL";
967            }
968    
969            // add package... some types have special mappings in corba
970            String packageName = (String) specialTypePackages.get(parameterType.getName());
971            if (packageName == null) {
972                packageName = getPackageName(parameterType.getName());
973            }
974            if (packageName.length() > 0) {
975                name += "_" + packageName;
976            }
977    
978            // arrays now contain a dimension indicator
979            if (arrayDimensions > 0) {
980                name += "_" + "seq" + arrayDimensions;
981            }
982    
983            // add the class name
984            String className = (String) specialTypeNames.get(parameterType.getName());
985            if (className == null) {
986                className = buildClassName(parameterType);
987            }
988            name += "_" + className;
989    
990            return name;
991        }
992    
993        /**
994         * Returns a string contianing an encoded class name.
995         */
996        private static String buildClassName(Class type) {
997            if (type.isArray()) {
998                throw new IllegalArgumentException("type is an array: " + type);
999            }
1000    
1001            // get the classname
1002            String typeName = type.getName();
1003            int endIndex = typeName.lastIndexOf('.');
1004            if (endIndex < 0) {
1005                return typeName;
1006            }
1007            StringBuffer className = new StringBuffer(typeName.substring(endIndex + 1));
1008    
1009            // for innerclasses replace the $ separator with two underscores
1010            // we can't just blindly replace all $ characters since class names can contain the $ character
1011            if (type.getDeclaringClass() != null) {
1012                String declaringClassName = getClassName(type.getDeclaringClass());
1013                assert className.toString().startsWith(declaringClassName + "$");
1014                className.replace(declaringClassName.length(), declaringClassName.length() + 1, "__");
1015            }
1016    
1017            // if we have a leading underscore prepend with J
1018            if (className.charAt(0) == '_') {
1019                className.insert(0, "J");
1020            }
1021            return className.toString();
1022        }
1023    
1024        private static String getClassName(Class type) {
1025            if (type.isArray()) {
1026                throw new IllegalArgumentException("type is an array: " + type);
1027            }
1028    
1029            // get the classname
1030            String typeName = type.getName();
1031            int endIndex = typeName.lastIndexOf('.');
1032            if (endIndex < 0) {
1033                return typeName;
1034            }
1035            return typeName.substring(endIndex + 1);
1036        }
1037    
1038        private static String getPackageName(String interfaceName) {
1039            int endIndex = interfaceName.lastIndexOf('.');
1040            if (endIndex < 0) {
1041                return "";
1042            }
1043            return interfaceName.substring(0, endIndex);
1044        }
1045    
1046        private static final Map specialTypeNames;
1047        private static final Map specialTypePackages;
1048        private static final Set keywords;
1049    
1050        static {
1051           specialTypeNames = new HashMap();
1052           specialTypeNames.put("boolean", "boolean");
1053           specialTypeNames.put("char", "wchar");
1054           specialTypeNames.put("byte", "octet");
1055           specialTypeNames.put("short", "short");
1056           specialTypeNames.put("int", "long");
1057           specialTypeNames.put("long", "long_long");
1058           specialTypeNames.put("float", "float");
1059           specialTypeNames.put("double", "double");
1060           specialTypeNames.put("java.lang.Class", "ClassDesc");
1061           specialTypeNames.put("java.lang.String", "WStringValue");
1062           specialTypeNames.put("org.omg.CORBA.Object", "Object");
1063    
1064           specialTypePackages = new HashMap();
1065           specialTypePackages.put("boolean", "");
1066           specialTypePackages.put("char", "");
1067           specialTypePackages.put("byte", "");
1068           specialTypePackages.put("short", "");
1069           specialTypePackages.put("int", "");
1070           specialTypePackages.put("long", "");
1071           specialTypePackages.put("float", "");
1072           specialTypePackages.put("double", "");
1073           specialTypePackages.put("java.lang.Class", "javax.rmi.CORBA");
1074           specialTypePackages.put("java.lang.String", "CORBA");
1075           specialTypePackages.put("org.omg.CORBA.Object", "");
1076    
1077           keywords = new HashSet();
1078           keywords.add("abstract");
1079           keywords.add("any");
1080           keywords.add("attribute");
1081           keywords.add("boolean");
1082           keywords.add("case");
1083           keywords.add("char");
1084           keywords.add("const");
1085           keywords.add("context");
1086           keywords.add("custom");
1087           keywords.add("default");
1088           keywords.add("double");
1089           keywords.add("enum");
1090           keywords.add("exception");
1091           keywords.add("factory");
1092           keywords.add("false");
1093           keywords.add("fixed");
1094           keywords.add("float");
1095           keywords.add("in");
1096           keywords.add("inout");
1097           keywords.add("interface");
1098           keywords.add("long");
1099           keywords.add("module");
1100           keywords.add("native");
1101           keywords.add("object");
1102           keywords.add("octet");
1103           keywords.add("oneway");
1104           keywords.add("out");
1105           keywords.add("private");
1106           keywords.add("public");
1107           keywords.add("raises");
1108           keywords.add("readonly");
1109           keywords.add("sequence");
1110           keywords.add("short");
1111           keywords.add("string");
1112           keywords.add("struct");
1113           keywords.add("supports");
1114           keywords.add("switch");
1115           keywords.add("true");
1116           keywords.add("truncatable");
1117           keywords.add("typedef");
1118           keywords.add("union");
1119           keywords.add("unsigned");
1120           keywords.add("valuebase");
1121           keywords.add("valuetype");
1122           keywords.add("void");
1123           keywords.add("wchar");
1124           keywords.add("wstring");
1125        }
1126    
1127    }