KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > soap > encoding > SOAPMappingRegistry


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  *
5  * Copyright (c) 2000 The Apache Software Foundation. All rights
6  * reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Apache Software Foundation (http://www.apache.org/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "SOAP" and "Apache Software Foundation" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact apache@apache.org.
31  *
32  * 5. Products derived from this software may not be called "Apache",
33  * nor may "Apache" appear in their name, without prior written
34  * permission of the Apache Software Foundation.
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  * SUCH DAMAGE.
48  * ====================================================================
49  *
50  * This software consists of voluntary contributions made by many
51  * individuals on behalf of the Apache Software Foundation and was
52  * originally based on software copyright (c) 2000, International
53  * Business Machines, Inc., http://www.apache.org. For more
54  * information on the Apache Software Foundation, please see
55  * <http://www.apache.org/>.
56  */

57
58 package org.apache.soap.encoding;
59
60 import java.io.*;
61 import java.util.*;
62 import java.math.*;
63 import org.w3c.dom.*;
64 import org.apache.soap.util.*;
65 import org.apache.soap.util.xml.*;
66 import org.apache.soap.*;
67 import org.apache.soap.rpc.*;
68 import org.apache.soap.encoding.literalxml.*;
69 import org.apache.soap.encoding.soapenc.*;
70 import org.apache.soap.encoding.xmi.*;
71
72 /**
73  * A <code>SOAPMappingRegistry</code> object is an
74  * <code>XMLJavaMappingRegistry</code> with pre-registered
75  * serializers and deserializers to support <em>SOAP</em>.
76  *
77  * @author Matthew J. Duftler (duftler@us.ibm.com)
78  * @author Sanjiva Weerawarana (sanjiva@watson.ibm.com)
79  * @author Francisco Curbera (curbera@us.ibm.com)
80  * @author Sam Ruby (rubys@us.ibm.com)
81  * @author Glen Daniels (gdaniels@allaire.com)
82  */

83 public class SOAPMappingRegistry extends XMLJavaMappingRegistry
84 {
85   private static SOAPMappingRegistry baseReg1999;
86   private static SOAPMappingRegistry baseReg2000;
87   private static SOAPMappingRegistry baseReg2001;
88
89   private SOAPMappingRegistry parent = null;
90   private String JavaDoc schemaURI;
91
92   private static String JavaDoc soapEncURI = Constants.NS_URI_SOAP_ENC;
93   
94   private static QName arrayQName = new QName(soapEncURI, "Array");
95
96   // create all the standard serializers/deserializers as static vars.
97
// these fill into all the various base registries.
98

99   private static StringDeserializer stringDeser = new StringDeserializer();
100   private static IntDeserializer intDeser = new IntDeserializer();
101   private static DecimalDeserializer decimalDeser = new DecimalDeserializer();
102   private static FloatDeserializer floatDeser = new FloatDeserializer();
103   private static DoubleDeserializer doubleDeser = new DoubleDeserializer();
104   private static BooleanDeserializer booleanDeser = new BooleanDeserializer();
105   private static LongDeserializer longDeser = new LongDeserializer();
106   private static ShortDeserializer shortDeser = new ShortDeserializer();
107   private static ByteDeserializer byteDeser = new ByteDeserializer();
108   private static HexDeserializer hexDeser = new HexDeserializer();
109
110   private static QNameSerializer qNameSer = new QNameSerializer();
111   private static ParameterSerializer paramSer = new ParameterSerializer();
112   private static ArraySerializer arraySer = new ArraySerializer();
113   private static VectorSerializer vectorSer = new VectorSerializer();
114   private static HashtableSerializer hashtableSer = new HashtableSerializer();
115   private static XMLParameterSerializer xmlParamSer =
116     new XMLParameterSerializer();
117   private static DateSerializer dateSer = new DateSerializer();
118   private static CalendarSerializer calSer = new CalendarSerializer();
119   private static UrTypeDeserializer objDeser = new UrTypeDeserializer();
120   public static MimePartSerializer partSer = new MimePartSerializer();
121   
122   /**
123    * The following stuff is here to deal with the slight differences
124    * between 1999 schema and 2000/10 schema. This system allows us to
125    * register type mappings for both sets of QNames, and to default to
126    * whichever one is set as current.
127    *
128    * !!! The order of the elements in these arrays is critical. Be
129    * careful when editing.
130    */

131   private static QName schema1999QNames [] = {
132     Constants.string1999QName,
133     Constants.int1999QName,
134     Constants.int1999QName,
135     Constants.decimal1999QName,
136     Constants.float1999QName,
137     Constants.float1999QName,
138     Constants.double1999QName,
139     Constants.double1999QName,
140     Constants.boolean1999QName,
141     Constants.boolean1999QName,
142     Constants.long1999QName,
143     Constants.long1999QName,
144     Constants.short1999QName,
145     Constants.short1999QName,
146     Constants.byte1999QName,
147     Constants.byte1999QName,
148     Constants.hex1999QName,
149     Constants.qName1999QName,
150     Constants.date1999QName,
151     Constants.timeInst1999QName,
152     Constants.object1999QName,
153     Constants.object1999QName,
154     Constants.object1999QName,
155     Constants.object1999QName,
156     Constants.object1999QName,
157   };
158   
159   private static QName schema2000QNames [] = {
160     Constants.string2000QName,
161     Constants.int2000QName,
162     Constants.int2000QName,
163     Constants.decimal2000QName,
164     Constants.float2000QName,
165     Constants.float2000QName,
166     Constants.double2000QName,
167     Constants.double2000QName,
168     Constants.boolean2000QName,
169     Constants.boolean2000QName,
170     Constants.long2000QName,
171     Constants.long2000QName,
172     Constants.short2000QName,
173     Constants.short2000QName,
174     Constants.byte2000QName,
175     Constants.byte2000QName,
176     Constants.hex2000QName,
177     Constants.qName2000QName,
178     Constants.date2000QName,
179     Constants.timeInst2000QName,
180     Constants.object2000QName,
181     Constants.object2000QName,
182     Constants.object2000QName,
183     Constants.object2000QName,
184     Constants.object2000QName,
185   };
186   
187   private static QName schema2001QNames [] = {
188     Constants.string2001QName,
189     Constants.int2001QName,
190     Constants.int2001QName,
191     Constants.decimal2001QName,
192     Constants.float2001QName,
193     Constants.float2001QName,
194     Constants.double2001QName,
195     Constants.double2001QName,
196     Constants.boolean2001QName,
197     Constants.boolean2001QName,
198     Constants.long2001QName,
199     Constants.long2001QName,
200     Constants.short2001QName,
201     Constants.short2001QName,
202     Constants.byte2001QName,
203     Constants.byte2001QName,
204     Constants.hex2001QName,
205     Constants.qName2001QName,
206     Constants.date2001QName,
207     Constants.timeInst2001QName,
208     Constants.object2001QName,
209     Constants.object2001QName,
210     Constants.object2001QName,
211     Constants.object2001QName,
212     Constants.object2001QName,
213   };
214
215   private static Class JavaDoc classes [] = {
216     String JavaDoc.class,
217     Integer JavaDoc.class,
218     int.class,
219     BigDecimal.class,
220     Float JavaDoc.class,
221     float.class,
222     Double JavaDoc.class,
223     double.class,
224     Boolean JavaDoc.class,
225     boolean.class,
226     Long JavaDoc.class,
227     long.class,
228     Short JavaDoc.class,
229     short.class,
230     Byte JavaDoc.class,
231     byte.class,
232     Hex.class,
233     QName.class,
234     GregorianCalendar.class,
235     Date.class,
236     javax.mail.internet.MimeBodyPart JavaDoc.class,
237     java.io.InputStream JavaDoc.class,
238     javax.activation.DataSource JavaDoc.class,
239     javax.activation.DataHandler JavaDoc.class,
240     Object JavaDoc.class,
241   };
242
243   /*
244     This serializer runs its content through a mechanism to replace
245     the characters {&, ", ', <, >} with their appropriate escape
246     sequences.
247   */

248   private static Serializer cleanSer = new Serializer()
249   {
250     public void marshall(String JavaDoc inScopeEncStyle, Class JavaDoc javaType, Object JavaDoc src,
251                          Object JavaDoc context, Writer sink, NSStack nsStack,
252                          XMLJavaMappingRegistry xjmr, SOAPContext ctx)
253       throws IllegalArgumentException JavaDoc, IOException {
254       nsStack.pushScope();
255
256       if (src == null)
257       {
258         SoapEncUtils.generateNullStructure(inScopeEncStyle, javaType, context,
259                                            sink, nsStack, xjmr);
260       }
261       else
262       {
263         SoapEncUtils.generateStructureHeader(inScopeEncStyle,
264                                              javaType,
265                                              context,
266                                              sink,
267                                              nsStack,
268                                              xjmr);
269         
270         sink.write(Utils.cleanString(src.toString()) + "</" + context + '>');
271       }
272       nsStack.popScope();
273     }
274   };
275
276   /*
277     This serializer does not apply escape sequences to its content.
278     This serializer should be used for numbers and other things that
279     will not have any of the following characters: {&, ", ', <, >}
280   */

281   private static Serializer ser = new Serializer()
282   {
283     public void marshall(String JavaDoc inScopeEncStyle, Class JavaDoc javaType, Object JavaDoc src,
284                          Object JavaDoc context, Writer sink, NSStack nsStack,
285                          XMLJavaMappingRegistry xjmr, SOAPContext ctx)
286       throws IllegalArgumentException JavaDoc, IOException {
287       nsStack.pushScope();
288
289       SoapEncUtils.generateStructureHeader(inScopeEncStyle,
290                                            javaType,
291                                            context,
292                                            sink,
293                                            nsStack,
294                                            xjmr);
295
296       sink.write(src + "</" + context + '>');
297
298       nsStack.popScope();
299     }
300   };
301
302   private static Serializer serializers [] = {
303     cleanSer, // String
304
ser, // Integer
305
ser, // int
306
ser, // BigDecimal
307
ser, // Float
308
ser, // float
309
ser, // Double
310
ser, // double
311
ser, // Boolean
312
ser, // boolean
313
ser, // Long
314
ser, // long
315
ser, // Short
316
ser, // short
317
ser, // Byte
318
ser, // byte
319
ser, // Hex
320
qNameSer, // QName
321
calSer, // GregorianCalendar
322
dateSer, // Date
323
partSer, // MimeBodyPart
324
partSer, // InputStream
325
partSer, // DataSource
326
partSer, // DataHandler
327
null, // Object
328
};
329
330   private static Deserializer deserializers [] = {
331     stringDeser,
332     null,
333     intDeser,
334     decimalDeser,
335     null,
336     floatDeser,
337     null,
338     doubleDeser,
339     null,
340     booleanDeser,
341     null,
342     longDeser,
343     null,
344     shortDeser,
345     null,
346     byteDeser,
347     hexDeser,
348     qNameSer,
349     calSer,
350     dateSer,
351     null,
352     null,
353     null,
354     null,
355     objDeser,
356   };
357
358   /**
359    * Create a new SMR. The resulting registry is aware of all
360    * pre-defined type mappings.
361    */

362   public SOAPMappingRegistry()
363   {
364     this.schemaURI = Constants.NS_URI_CURRENT_SCHEMA_XSD;
365     this.parent = getBaseRegistry (Constants.NS_URI_CURRENT_SCHEMA_XSD);
366   }
367
368     /**
369      * This constructor takes a "parent" registry as a base registry.
370      * Lookup requests cascade up to the parent while registration
371      * requests stay here.
372      */

373     public SOAPMappingRegistry(SOAPMappingRegistry parent)
374     {
375       this(parent, Constants.NS_URI_CURRENT_SCHEMA_XSD);
376     }
377
378     /**
379     * This constructor is the base constructor. If parent is null, then this
380      * is viewed as a base registry and the registry is initialized with
381      * all the default mappings etc.. If it is not-null, the no init
382      * is done and the parent is assumed to have the stuff in it already.
383      *
384      * @param parent the "parent" SMR to delegate lookups to if I can't
385      * find the stuff in my tables. If parent is null, then I get
386      * pre-loaded with all the default type mappings etc. (some
387      * of which are based on the schema URI). If parent is not null,
388      * the default stuff is not put in - the idea is that in that
389      * case the parent already contains the defaults.
390      * @param schemaURI the namespace URI of XSD to be used for serializers.
391      * Deserializers for all 3 XSD URIs are always registered.
392      */

393     public SOAPMappingRegistry(SOAPMappingRegistry parent, String JavaDoc schemaURI)
394     {
395       this.parent = parent;
396       if (parent == null) {
397         initializeRegistry(schemaURI);
398       }
399       this.schemaURI = schemaURI;
400     }
401
402   /**
403    * Return the singleton registry instance configured for the
404    * indicated schema URI. If the schemaURI is unrecognized, the
405    * 2001 base registry is returned.
406    */

407   public static SOAPMappingRegistry getBaseRegistry (String JavaDoc schemaURI) {
408     synchronized (SOAPMappingRegistry.class) {
409       if (baseReg1999 == null) {
410     baseReg1999 =
411       new SOAPMappingRegistry (null, Constants.NS_URI_1999_SCHEMA_XSD);
412     baseReg2000 =
413       new SOAPMappingRegistry (null, Constants.NS_URI_2000_SCHEMA_XSD);
414     baseReg2001 =
415       new SOAPMappingRegistry (null, Constants.NS_URI_2001_SCHEMA_XSD);
416       }
417     }
418
419     if (schemaURI.equals(Constants.NS_URI_1999_SCHEMA_XSD)) {
420       return baseReg1999;
421     } else if (schemaURI.equals(Constants.NS_URI_2000_SCHEMA_XSD)) {
422       return baseReg2000;
423     } else {
424       return baseReg2001;
425     }
426   }
427
428   /** Set the default encoding style. If the query*() calls
429    * are invoked with a null encodingStyleURI parameter, we'll
430    * use this instead.
431    */

432   public void setDefaultEncodingStyle(String JavaDoc defEncStyle) {
433       super.setDefaultEncodingStyle(defEncStyle);
434       if (parent != null)
435           parent.setDefaultEncodingStyle(defEncStyle);
436   }
437
438   /**
439    * Return the schemaURI that was used to create this registry
440    * instance.
441    */

442   public String JavaDoc getSchemaURI () {
443     return schemaURI;
444   }
445
446   /**
447    * Returns the "parent" registry, if there is one. Otherwise,
448    * returns null.
449    */

450   public SOAPMappingRegistry getParent () {
451     return parent;
452   }
453
454   /**
455    * Initialize myself with all the built-in default stuff. If schemaURI
456    * is not recognized, defaults to 2001 schema URI.
457    */

458   private void initializeRegistry(String JavaDoc schemaURI)
459   {
460     QName schemaQNames[] = null;
461
462     if (schemaURI.equals(Constants.NS_URI_1999_SCHEMA_XSD)) {
463       schemaQNames = schema1999QNames;
464       mapSchemaTypes(schema2000QNames, false);
465       mapSchemaTypes(schema2001QNames, false);
466     } else if (schemaURI.equals(Constants.NS_URI_2000_SCHEMA_XSD)) {
467       mapSchemaTypes(schema1999QNames, false);
468       schemaQNames = schema2000QNames;
469       mapSchemaTypes(schema2001QNames, false);
470     } else {
471       if (!schemaURI.equals(Constants.NS_URI_2001_SCHEMA_XSD)) {
472     System.err.println("WARNING: Unrecognized Schema URI '" + schemaURI +
473                "' specified. Defaulting to '" +
474                Constants.NS_URI_2001_SCHEMA_XSD + "'.");
475       }
476       mapSchemaTypes(schema1999QNames, false);
477       mapSchemaTypes(schema2000QNames, false);
478       schemaQNames = schema2001QNames;
479     }
480     
481     // map the ones that I want to do read-write with
482
mapSchemaTypes(schemaQNames, true);
483
484     // Register parameter serializer for SOAP-ENC encoding style.
485
mapTypes(soapEncURI, RPCConstants.Q_ELEM_PARAMETER, Parameter.class,
486              paramSer, paramSer);
487
488     // Register array deserializer for SOAP-ENC encoding style.
489
mapTypes(soapEncURI, arrayQName, null, null, arraySer);
490
491     // Register parameter serializer for literal xml encoding style.
492
mapTypes(Constants.NS_URI_LITERAL_XML, RPCConstants.Q_ELEM_PARAMETER,
493              Parameter.class, xmlParamSer, xmlParamSer);
494
495     try {
496       Class JavaDoc XMISerializer =
497         Class.forName("org.apache.soap.util.xml.XMISerializer");
498       Class JavaDoc XMIParameterSerializer =
499         Class.forName("org.apache.soap.encoding.xmi.XMIParameterSerializer");
500
501       // Register default serializers for XMI encoding style.
502
mapTypes(Constants.NS_URI_XMI_ENC, null, null,
503                (Serializer)XMISerializer.newInstance(),
504                (Deserializer)XMIParameterSerializer.newInstance());
505
506       // Register serializer for Parameter class - not deserializer!
507
mapTypes(Constants.NS_URI_XMI_ENC, null, Parameter.class,
508                (Serializer)XMIParameterSerializer.newInstance(), null);
509     } catch (IllegalAccessException JavaDoc iae) {
510     } catch (InstantiationException JavaDoc ie) {
511     } catch (ClassNotFoundException JavaDoc cnfe) {
512     } catch (NoClassDefFoundError JavaDoc ncdfe) {
513       // If the class can't be loaded, continue without it...
514
}
515
516     /*
517       Basic collection types - these should map fine to Perl, Python, C++...
518       (but an encoding like this needs to be agreed upon)
519     */

520     mapTypes(soapEncURI, new QName(Constants.NS_URI_XML_SOAP, "Vector"),
521              Vector.class, vectorSer, vectorSer);
522     mapTypes(soapEncURI, new QName(Constants.NS_URI_XML_SOAP, "Map"),
523              Hashtable.class, hashtableSer, hashtableSer);
524
525     try {
526       Class JavaDoc mapClass = Class.forName("java.util.Map");
527       MapSerializer mapSer = new MapSerializer();
528
529       mapTypes(soapEncURI, new QName(Constants.NS_URI_XML_SOAP, "Map"),
530                mapClass, mapSer, mapSer);
531     } catch (ClassNotFoundException JavaDoc cnfe) {
532     } catch (NoClassDefFoundError JavaDoc ncdfe) {
533       // If the class can't be loaded, continue without it...
534
}
535
536     /*
537       Map a Java byte array to the SOAP-ENC:base64 subtype.
538     */

539     Base64Serializer base64Ser = new Base64Serializer();
540     QName base64QName = new QName(Constants.NS_URI_2001_SCHEMA_XSD, "base64Binary");
541     mapTypes(soapEncURI, base64QName, byte[].class, base64Ser, base64Ser);
542     base64QName = new QName(soapEncURI, "base64");
543     mapTypes(soapEncURI, base64QName, byte[].class, base64Ser, base64Ser);
544   }
545   
546   /**
547    * Map a set of schema types defined in the arrays above. If
548    * the "serialize" arg is set to true, we'll map the serializer
549    * side (i.e. when output gets generated it'll be as those QNames),
550    * otherwise we just do deserializers.
551    */

552   private void mapSchemaTypes(QName [] schemaQNames, boolean serialize)
553   {
554     for (int i = 0; i < schemaQNames.length; i++) {
555       QName qName = schemaQNames[i];
556       Class JavaDoc cls = classes[i];
557       Serializer ser = serialize ? serializers[i] : null;
558       Deserializer dser = deserializers[i];
559       
560       mapTypes(soapEncURI, qName, cls, ser, dser);
561     }
562   }
563
564   /**
565    * This function overrides the one in XMLJavaMappingRegistry for the sole
566    * purpose of returning SOAP-ENC:Array when javaType represents an array.
567    * The XMLJavaMappingRegistry will be consulted first, and if no mapping
568    * is found, SOAP-ENC:Array is returned. Obviously, this only applies when
569    * the encoding style is soap encoding.
570    */

571   protected QName queryElementType_(Class JavaDoc javaType, String JavaDoc encodingStyleURI)
572   {
573     QName qn = super.queryElementType_(javaType, encodingStyleURI);
574     if (qn != null) {
575       return qn;
576     }
577     if (parent != null) {
578       qn = parent.queryElementType_(javaType, encodingStyleURI);
579       if (qn != null) {
580         return qn;
581       }
582     }
583
584     if (javaType != null
585         && (javaType.isArray())
586         && encodingStyleURI != null
587         && encodingStyleURI.equals(soapEncURI)) {
588       return arrayQName;
589     }
590
591     return null;
592   }
593
594   /**
595    * This function overrides the one in XMLJavaMappingRegistry for the sole
596    * purpose of returning an ArraySerializer when javaType represents an
597    * array. The XMLJavaMappingRegistry will be consulted first, and if no
598    * serializer is found for javaType, ArraySerailizer is returned.
599    * Obviously, this only applies when the encoding style is soap encoding.
600    */

601   protected Serializer querySerializer_(Class JavaDoc javaType,
602                                         String JavaDoc encodingStyleURI)
603   {
604     Serializer s = super.querySerializer_(javaType, encodingStyleURI);
605     if (s != null) {
606       return s;
607     }
608     if (parent != null) {
609       s = parent.querySerializer_(javaType, encodingStyleURI);
610       if (s != null) {
611         return s;
612       }
613     }
614
615     if (javaType != null
616         && encodingStyleURI != null
617         && encodingStyleURI.equals(soapEncURI)) {
618       if (javaType.isArray()) {
619         return arraySer;
620       }
621     }
622
623     return null;
624   }
625
626   /**
627    * Override the query deserializer to look at the parent too before
628    * saying that a deserializer is not available.
629    */

630   protected Deserializer queryDeserializer_(QName elementType,
631                                             String JavaDoc encodingStyleURI)
632   {
633     Deserializer ds = super.queryDeserializer_(elementType, encodingStyleURI);
634     if (ds != null) {
635       return ds;
636     }
637     if (parent != null) {
638       ds = parent.queryDeserializer_(elementType, encodingStyleURI);
639       if (ds != null) {
640         return ds;
641       }
642     }
643     return null;
644   }
645
646   /**
647    * Overide the query Javatype to look at the parent too before
648    * saying that a Java type is not available.
649    */

650   protected Class JavaDoc queryJavaType_(QName elementType, String JavaDoc encodingStyleURI)
651   {
652     Class JavaDoc jt = super.queryJavaType_(elementType, encodingStyleURI);
653     if (jt != null) {
654       return jt;
655     }
656     if (parent != null) {
657       jt = parent.queryJavaType_(elementType, encodingStyleURI);
658       if (jt != null) {
659         return jt;
660       }
661     }
662     return null;
663   }
664 }
665
Popular Tags