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