src/share/jaxws_classes/com/sun/xml/internal/ws/binding/WebServiceFeatureList.java

Tue, 09 Apr 2013 14:51:13 +0100

author
alanb
date
Tue, 09 Apr 2013 14:51:13 +0100
changeset 368
0989ad8c0860
parent 286
f50545b5e2f1
child 408
b0610cd08440
permissions
-rw-r--r--

8010393: Update JAX-WS RI to 2.2.9-b12941
Reviewed-by: alanb, erikj
Contributed-by: miroslav.kos@oracle.com, martin.grebac@oracle.com

     1 /*
     2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package com.sun.xml.internal.ws.binding;
    28 import com.sun.istack.internal.NotNull;
    29 import com.sun.istack.internal.Nullable;
    30 import com.sun.xml.internal.ws.api.BindingID;
    31 import com.sun.xml.internal.ws.api.FeatureListValidator;
    32 import com.sun.xml.internal.ws.api.FeatureListValidatorAnnotation;
    33 import com.sun.xml.internal.ws.api.ImpliesWebServiceFeature;
    34 import com.sun.xml.internal.ws.api.SOAPVersion;
    35 import com.sun.xml.internal.ws.api.WSBinding;
    36 import com.sun.xml.internal.ws.api.WSFeatureList;
    37 import com.sun.xml.internal.ws.api.FeatureConstructor;
    38 import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort;
    39 import com.sun.xml.internal.ws.api.model.wsdl.WSDLFeaturedObject;
    40 import com.sun.xml.internal.ws.model.RuntimeModelerException;
    41 import com.sun.xml.internal.ws.model.wsdl.WSDLPortImpl;
    42 import com.sun.xml.internal.ws.resources.ModelerMessages;
    43 import com.sun.xml.internal.bind.util.Which;
    45 import javax.xml.ws.RespectBinding;
    46 import javax.xml.ws.RespectBindingFeature;
    47 import javax.xml.ws.WebServiceException;
    48 import javax.xml.ws.WebServiceFeature;
    49 import javax.xml.ws.soap.Addressing;
    50 import javax.xml.ws.soap.AddressingFeature;
    51 import javax.xml.ws.soap.MTOM;
    52 import javax.xml.ws.soap.MTOMFeature;
    53 import javax.xml.ws.spi.WebServiceFeatureAnnotation;
    55 import com.oracle.webservices.internal.api.EnvelopeStyleFeature;
    57 import java.lang.annotation.Annotation;
    58 import java.lang.reflect.InvocationTargetException;
    59 import java.lang.reflect.Method;
    60 import java.lang.reflect.Constructor;
    61 import java.util.*;
    62 import java.util.logging.Logger;
    64 /**
    65  * Represents a list of {@link WebServiceFeature}s that has bunch of utility
    66  * methods pertaining to web service features.
    67  *
    68  * @author Rama Pulavarthi
    69  */
    70 public final class WebServiceFeatureList extends AbstractMap<Class<? extends WebServiceFeature>, WebServiceFeature> implements WSFeatureList {
    71     public static WebServiceFeatureList toList(Iterable<WebServiceFeature> features) {
    72         if (features instanceof WebServiceFeatureList)
    73             return (WebServiceFeatureList) features;
    74         WebServiceFeatureList w = new WebServiceFeatureList();
    75         if (features != null)
    76             w.addAll(features);
    77         return w;
    78     }
    80     private Map<Class<? extends WebServiceFeature>, WebServiceFeature> wsfeatures = new HashMap<Class<? extends WebServiceFeature>, WebServiceFeature>();
    81     private boolean isValidating = false;
    83     public WebServiceFeatureList() {
    84     }
    86     /**
    87      * Delegate to this parent if non-null.
    88      */
    89     private @Nullable
    90         WSDLFeaturedObject parent;
    92     public WebServiceFeatureList(@NotNull WebServiceFeature... features) {
    93         if (features != null) {
    94             for (WebServiceFeature f : features) {
    95                 addNoValidate(f);
    96             }
    97         }
    98     }
   100     public void validate() {
   101         if (!isValidating) {
   102             isValidating = true;
   104             // validation
   105             for (WebServiceFeature ff : this) {
   106                 validate(ff);
   107             }
   108         }
   109     }
   111     private void validate(WebServiceFeature feature) {
   112         // run validation
   113         FeatureListValidatorAnnotation fva = feature.getClass().getAnnotation(FeatureListValidatorAnnotation.class);
   114         if (fva != null) {
   115             Class<? extends FeatureListValidator> beanClass = fva.bean();
   116             try {
   117                 FeatureListValidator validator = beanClass.newInstance();
   118                 validator.validate(this);
   119             } catch (InstantiationException e) {
   120                 throw new WebServiceException(e);
   121             } catch (IllegalAccessException e) {
   122                 throw new WebServiceException(e);
   123             }
   124         }
   125     }
   127     public WebServiceFeatureList(WebServiceFeatureList features) {
   128         if (features != null) {
   129             wsfeatures.putAll(features.wsfeatures);
   130             parent = features.parent;
   131             isValidating = features.isValidating;
   132         }
   133     }
   135     /**
   136      * Creates a list by reading featuers from the annotation on a class.
   137      */
   138     public WebServiceFeatureList(@NotNull Class<?> endpointClass) {
   139         parseAnnotations(endpointClass);
   140     }
   142     /**
   143      * Adds the corresponding features to the list for feature annotations(i.e
   144      * which have {@link WebServiceFeatureAnnotation} meta annotation)
   145      *
   146      * @param annIt collection of annotations(that can have non-feature annotations)
   147      */
   148     public void parseAnnotations(Iterable<Annotation> annIt) {
   149         for(Annotation ann : annIt) {
   150             WebServiceFeature feature = getFeature(ann);
   151             if (feature != null) {
   152                 add(feature);
   153             }
   154         }
   155     }
   157     /**
   158      * Returns a corresponding feature for a feature annotation(i.e which has
   159      * {@link WebServiceFeatureAnnotation} meta annotation)
   160      *
   161      * @return corresponding feature for the annotation
   162      *         null, if the annotation is nota feature annotation
   163      */
   164     public static WebServiceFeature getFeature(Annotation a) {
   165         WebServiceFeature ftr = null;
   166         if (!(a.annotationType().isAnnotationPresent(WebServiceFeatureAnnotation.class))) {
   167             ftr = null;
   168         } else if (a instanceof Addressing) {
   169             Addressing addAnn = (Addressing) a;
   170             try {
   171                 ftr = new AddressingFeature(addAnn.enabled(), addAnn.required(),addAnn.responses());
   172             } catch(NoSuchMethodError e) {
   173                 //throw error. We can't default to Responses.ALL as we dont know if the user has not used 2.2 annotation with responses.
   174                 throw new RuntimeModelerException(ModelerMessages.RUNTIME_MODELER_ADDRESSING_RESPONSES_NOSUCHMETHOD(toJar(Which.which(Addressing.class))));
   175             }
   176         } else if (a instanceof MTOM) {
   177             MTOM mtomAnn = (MTOM) a;
   178             ftr = new MTOMFeature(mtomAnn.enabled(), mtomAnn.threshold());
   179         } else if (a instanceof RespectBinding) {
   180             RespectBinding rbAnn = (RespectBinding) a;
   181             ftr = new RespectBindingFeature(rbAnn.enabled());
   182         } else {
   183             ftr = getWebServiceFeatureBean(a);
   184         }
   185         return ftr;
   186     }
   188     /**
   189      *
   190      * @param endpointClass web service impl class
   191      */
   192     public void parseAnnotations(Class<?> endpointClass) {
   193         for (Annotation a : endpointClass.getAnnotations()) {
   194             WebServiceFeature ftr = getFeature(a);
   195             if (ftr != null) {
   196                 if (ftr instanceof MTOMFeature) {
   197                     // check conflict with @BindingType
   198                     BindingID bindingID = BindingID.parse(endpointClass);
   199                     MTOMFeature bindingMtomSetting = bindingID.createBuiltinFeatureList().get(MTOMFeature.class);
   200                     if (bindingMtomSetting != null && bindingMtomSetting.isEnabled() ^ ftr.isEnabled()) {
   201                         throw new RuntimeModelerException(
   202                             ModelerMessages.RUNTIME_MODELER_MTOM_CONFLICT(bindingID, ftr.isEnabled()));
   203                     }
   204                 }
   205                 add(ftr);
   206             }
   207         }
   208     }
   210     /**
   211      * Given the URL String inside jar, returns the URL to the jar itself.
   212      */
   213     private static String toJar(String url) {
   214         if(!url.startsWith("jar:"))
   215             return url;
   216         url = url.substring(4); // cut off jar:
   217         return url.substring(0,url.lastIndexOf('!'));    // cut off everything after '!'
   218     }
   220     private static WebServiceFeature getWebServiceFeatureBean(Annotation a) {
   221         WebServiceFeatureAnnotation wsfa = a.annotationType().getAnnotation(WebServiceFeatureAnnotation.class);
   222         Class<? extends WebServiceFeature> beanClass = wsfa.bean();
   223         WebServiceFeature bean;
   225         Constructor ftrCtr = null;
   226         String[] paramNames = null;
   227         for (Constructor con : beanClass.getConstructors()) {
   228             FeatureConstructor ftrCtrAnn = (FeatureConstructor) con.getAnnotation(FeatureConstructor.class);
   229             if (ftrCtrAnn != null) {
   230                 if (ftrCtr == null) {
   231                     ftrCtr = con;
   232                     paramNames = ftrCtrAnn.value();
   233                 } else {
   234                     throw new WebServiceException(
   235                         ModelerMessages.RUNTIME_MODELER_WSFEATURE_MORETHANONE_FTRCONSTRUCTOR(a, beanClass));
   236                 }
   237             }
   238         }
   239         if (ftrCtr == null) {
   240             bean = getWebServiceFeatureBeanViaBuilder(a, beanClass);
   241             if (bean != null) {
   242                 return bean;
   243             } else {
   244                 throw new WebServiceException(
   245                     ModelerMessages.RUNTIME_MODELER_WSFEATURE_NO_FTRCONSTRUCTOR(a, beanClass));
   246             }
   247         }
   248         if (ftrCtr.getParameterTypes().length != paramNames.length) {
   249             throw new WebServiceException(
   250                 ModelerMessages.RUNTIME_MODELER_WSFEATURE_ILLEGAL_FTRCONSTRUCTOR(a, beanClass));
   251         }
   253         try {
   254             Object[] params = new Object[paramNames.length];
   255             for (int i = 0; i < paramNames.length; i++) {
   256                 Method m = a.annotationType().getDeclaredMethod(paramNames[i]);
   257                 params[i] = m.invoke(a);
   258             }
   259             bean = (WebServiceFeature) ftrCtr.newInstance(params);
   260         } catch (Exception e) {
   261             throw new WebServiceException(e);
   262         }
   263         return bean;
   264     }
   266     private static WebServiceFeature getWebServiceFeatureBeanViaBuilder(
   267         final Annotation annotation,
   268         final Class<? extends WebServiceFeature> beanClass)
   269     {
   270         try {
   271             final Method featureBuilderMethod = beanClass.getDeclaredMethod("builder");
   272             final Object builder = featureBuilderMethod.invoke(beanClass);
   273             final Method buildMethod = builder.getClass().getDeclaredMethod("build");
   275             for (Method builderMethod : builder.getClass().getDeclaredMethods()) {
   276                 if (!builderMethod.equals(buildMethod)) {
   277                     final String methodName = builderMethod.getName();
   278                     final Method annotationMethod = annotation.annotationType().getDeclaredMethod(methodName);
   279                     final Object annotationFieldValue = annotationMethod.invoke(annotation);
   280                     final Object[] arg = { annotationFieldValue };
   281                     if (skipDuringOrgJvnetWsToComOracleWebservicesPackageMove(builderMethod, annotationFieldValue)) {
   282                         continue;
   283                     }
   284                     builderMethod.invoke(builder, arg);
   285                 }
   286             }
   288             final Object result = buildMethod.invoke(builder);
   289             if (result instanceof WebServiceFeature) {
   290                 return (WebServiceFeature) result;
   291             } else {
   292                 throw new WebServiceException("Not a WebServiceFeature: " + result);
   293             }
   294         } catch (final NoSuchMethodException e) {
   295             return null;
   296         } catch (final IllegalAccessException e) {
   297             throw new WebServiceException(e);
   298         } catch (final InvocationTargetException e) {
   299             throw new WebServiceException(e);
   300         }
   301     }
   303     // TODO this will be removed after package move is complete.
   304     private static boolean skipDuringOrgJvnetWsToComOracleWebservicesPackageMove(
   305         final Method builderMethod,
   306         final Object annotationFieldValue)
   307     {
   308         final Class<?> annotationFieldValueClass = annotationFieldValue.getClass();
   309         if (! annotationFieldValueClass.isEnum()) {
   310             return false;
   311         }
   312         final Class<?>[] builderMethodParameterTypes = builderMethod.getParameterTypes();
   313         if (builderMethodParameterTypes.length != 1) {
   314             throw new WebServiceException("expected only 1 parameter");
   315         }
   316         final String builderParameterTypeName = builderMethodParameterTypes[0].getName();
   317         if (! builderParameterTypeName.startsWith("com.oracle.webservices.internal.test.features_annotations_enums.apinew") &&
   318             ! builderParameterTypeName.startsWith("com.oracle.webservices.internal.api")) {
   319             return false;
   320         }
   321         return false;
   322     }
   324     public Iterator<WebServiceFeature> iterator() {
   325         if (parent != null)
   326             return new MergedFeatures(parent.getFeatures());
   327         return wsfeatures.values().iterator();
   328     }
   330     public @NotNull
   331         WebServiceFeature[] toArray() {
   332         if (parent != null)
   333             return new MergedFeatures(parent.getFeatures()).toArray();
   334         return wsfeatures.values().toArray(new WebServiceFeature[] {});
   335     }
   337     public boolean isEnabled(@NotNull Class<? extends WebServiceFeature> feature) {
   338         WebServiceFeature ftr = get(feature);
   339         return ftr != null && ftr.isEnabled();
   340     }
   342     public boolean contains(@NotNull Class<? extends WebServiceFeature> feature) {
   343         WebServiceFeature ftr = get(feature);
   344         return ftr != null;
   345     }
   347     public @Nullable
   348         <F extends WebServiceFeature> F get(@NotNull Class<F> featureType) {
   349         WebServiceFeature f = featureType.cast(wsfeatures.get(featureType));
   350         if (f == null && parent != null) {
   351             return parent.getFeatures().get(featureType);
   352         }
   353         return (F) f;
   354     }
   356     /**
   357      * Adds a feature to the list if it's not already added.
   358      */
   359     public void add(@NotNull WebServiceFeature f) {
   360         if(addNoValidate(f) && isValidating)
   361             validate(f);
   362     }
   364     private boolean addNoValidate(@NotNull WebServiceFeature f) {
   365         if (!wsfeatures.containsKey(f.getClass())) {
   366             wsfeatures.put(f.getClass(), f);
   368             if (f instanceof ImpliesWebServiceFeature)
   369                 ((ImpliesWebServiceFeature) f).implyFeatures(this);
   371             return true;
   372         }
   374         return false;
   375     }
   377     /**
   378      * Adds features to the list if it's not already added.
   379      */
   380     public void addAll(@NotNull Iterable<WebServiceFeature> list) {
   381         for (WebServiceFeature f : list)
   382             add(f);
   383     }
   385     /**
   386      * Sets MTOM feature, overriding any existing feature.  This is necessary for compatibility
   387      * with the existing {@link SOAPBinding.setMTOMEnabled}.
   388      * @param b if MTOM will be enabled
   389      */
   390     void setMTOMEnabled(boolean b) {
   391         wsfeatures.put(MTOMFeature.class, new MTOMFeature(b));
   392     }
   394     public boolean equals(Object other) {
   395         if (!(other instanceof WebServiceFeatureList))
   396             return false;
   398         WebServiceFeatureList w = (WebServiceFeatureList) other;
   399         return wsfeatures.equals(w.wsfeatures) && (parent == w.parent);
   400     }
   402     public String toString() {
   403         return wsfeatures.toString();
   404     }
   406     /**
   407      * Merges the extra features that are not already set on binding.
   408      * i.e, if a feature is set already on binding through some other API
   409      * the corresponding wsdlFeature is not set.
   410      *
   411      * @param features          Web Service features that need to be merged with already configured features.
   412      * @param reportConflicts   If true, checks if the feature setting in WSDL (wsdl extension or
   413      *                          policy configuration) conflicts with feature setting in Deployed Service and
   414      *                          logs warning if there are any conflicts.
   415      */
   416     public void mergeFeatures(@NotNull Iterable<WebServiceFeature> features, boolean reportConflicts) {
   417         for (WebServiceFeature wsdlFtr : features) {
   418             if (get(wsdlFtr.getClass()) == null) {
   419                 add(wsdlFtr);
   420             } else if (reportConflicts) {
   421                 if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
   422                     LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
   423                                        get(wsdlFtr.getClass()), wsdlFtr));
   424                 }
   425             }
   426         }
   427     }
   429     public void mergeFeatures(WebServiceFeature[] features, boolean reportConflicts) {
   430         for (WebServiceFeature wsdlFtr : features) {
   431             if (get(wsdlFtr.getClass()) == null) {
   432                 add(wsdlFtr);
   433             } else if (reportConflicts) {
   434                 if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
   435                     LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
   436                                        get(wsdlFtr.getClass()), wsdlFtr));
   437                 }
   438             }
   439         }
   440     }
   442     /**
   443      * Extracts features from {@link WSDLPortImpl#getFeatures()}. Extra features
   444      * that are not already set on binding. i.e, if a feature is set already on
   445      * binding through someother API the coresponding wsdlFeature is not set.
   446      *
   447      * @param wsdlPort
   448      *            WSDLPort model
   449      * @param honorWsdlRequired
   450      *            If this is true add WSDL Feature only if wsd:Required=true In
   451      *            SEI case, it should be false In Provider case, it should be
   452      *            true
   453      * @param reportConflicts
   454      *            If true, checks if the feature setting in WSDL (wsdl extension
   455      *            or policy configuration) colflicts with feature setting in
   456      *            Deployed Service and logs warning if there are any conflicts.
   457      */
   458     public void mergeFeatures(@NotNull WSDLPort wsdlPort,
   459                               boolean honorWsdlRequired, boolean reportConflicts) {
   460         if (honorWsdlRequired && !isEnabled(RespectBindingFeature.class))
   461             return;
   462         if (!honorWsdlRequired) {
   463             addAll(wsdlPort.getFeatures());
   464             return;
   465         }
   466         // Add only if isRequired returns true, when honorWsdlRequired is true
   467         for (WebServiceFeature wsdlFtr : wsdlPort.getFeatures()) {
   468             if (get(wsdlFtr.getClass()) == null) {
   469                 try {
   470                     // if it is a WSDL Extension , it will have required
   471                     // attribute
   472                     Method m = (wsdlFtr.getClass().getMethod("isRequired"));
   473                     try {
   474                         boolean required = (Boolean) m.invoke(wsdlFtr);
   475                         if (required)
   476                             add(wsdlFtr);
   477                     } catch (IllegalAccessException e) {
   478                         throw new WebServiceException(e);
   479                     } catch (InvocationTargetException e) {
   480                         throw new WebServiceException(e);
   481                     }
   482                 } catch (NoSuchMethodException e) {
   483                     // this wsdlFtr is not an WSDL extension, just add it
   484                     add(wsdlFtr);
   485                 }
   486             } else if (reportConflicts) {
   487                 if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
   488                     LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
   489                                        get(wsdlFtr.getClass()), wsdlFtr));
   490                 }
   492             }
   493         }
   494     }
   496     /**
   497      * Set the parent features. Basically the parent feature list will be
   498      * overriden by this feature list.
   499      */
   500     public void setParentFeaturedObject(@NotNull WSDLFeaturedObject parent) {
   501         this.parent = parent;
   502     }
   504     public static @Nullable <F extends WebServiceFeature> F getFeature(@NotNull WebServiceFeature[] features,
   505                                                                        @NotNull Class<F> featureType) {
   506         for (WebServiceFeature f : features) {
   507             if (f.getClass() == featureType)
   508                 return (F) f;
   509         }
   510         return null;
   511     }
   513     /**
   514      * A Union of this WebServiceFeatureList and the parent.
   515      */
   516     private final class MergedFeatures implements Iterator<WebServiceFeature> {
   517         private final Stack<WebServiceFeature> features = new Stack<WebServiceFeature>();
   519         public MergedFeatures(@NotNull WSFeatureList parent) {
   521             for (WebServiceFeature f : wsfeatures.values()) {
   522                 features.push(f);
   523             }
   525             for (WebServiceFeature f : parent) {
   526                 if (!wsfeatures.containsKey(f.getClass())) {
   527                     features.push(f);
   528                 }
   529             }
   530         }
   532         public boolean hasNext() {
   533             return !features.empty();
   534         }
   536         public WebServiceFeature next() {
   537             if (!features.empty()) {
   538                 return features.pop();
   539             }
   540             throw new NoSuchElementException();
   541         }
   543         public void remove() {
   544             if (!features.empty()) {
   545                 features.pop();
   546             }
   547         }
   549         public WebServiceFeature[] toArray() {
   550             return features.toArray(new WebServiceFeature[] {});
   551         }
   552     }
   554     private static final Logger LOGGER = Logger.getLogger(WebServiceFeatureList.class.getName());
   556     @Override
   557     public Set<java.util.Map.Entry<Class<? extends WebServiceFeature>, WebServiceFeature>> entrySet() {
   558         return wsfeatures.entrySet();
   559     }
   561     @Override
   562     public WebServiceFeature put(Class<? extends WebServiceFeature> key, WebServiceFeature value) {
   563         return wsfeatures.put(key, value);
   564     }
   566     static public SOAPVersion getSoapVersion(WSFeatureList features) {
   567         {
   568             EnvelopeStyleFeature env = features.get(EnvelopeStyleFeature.class);
   569             if (env != null) {
   570                 return SOAPVersion.from(env);
   571             }
   572         }
   573         com.oracle.webservices.internal.api.EnvelopeStyleFeature env = features.get(com.oracle.webservices.internal.api.EnvelopeStyleFeature.class);
   574         return env != null ? SOAPVersion.from(env) : null;
   575     }
   577     static public boolean isFeatureEnabled(Class<? extends WebServiceFeature> type, WebServiceFeature[] features) {
   578         WebServiceFeature ftr = getFeature(features, type);
   579         return ftr != null && ftr.isEnabled();
   580     }
   582     static public WebServiceFeature[] toFeatureArray(WSBinding binding) {
   583         //TODO scchen convert BindingID  to WebServiceFeature[]
   584         if(!binding.isFeatureEnabled(EnvelopeStyleFeature.class)) {
   585             WebServiceFeature[] f = { binding.getSOAPVersion().toFeature() };
   586             binding.getFeatures().mergeFeatures(f, false);
   587         }
   588         return binding.getFeatures().toArray();
   589     }
   590 }

mercurial