1/*
2 * Copyright (c) 1997, 2014, 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 */
25
26package com.sun.xml.internal.ws.binding;
27
28import com.sun.istack.internal.NotNull;
29import com.sun.istack.internal.Nullable;
30import com.sun.xml.internal.ws.api.BindingID;
31import com.sun.xml.internal.ws.api.FeatureListValidator;
32import com.sun.xml.internal.ws.api.FeatureListValidatorAnnotation;
33import com.sun.xml.internal.ws.api.ImpliesWebServiceFeature;
34import com.sun.xml.internal.ws.api.SOAPVersion;
35import com.sun.xml.internal.ws.api.WSBinding;
36import com.sun.xml.internal.ws.api.WSFeatureList;
37import com.sun.xml.internal.ws.api.FeatureConstructor;
38import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort;
39import com.sun.xml.internal.ws.api.model.wsdl.WSDLFeaturedObject;
40import com.sun.xml.internal.ws.model.RuntimeModelerException;
41import com.sun.xml.internal.ws.resources.ModelerMessages;
42import com.sun.xml.internal.bind.util.Which;
43
44import javax.xml.ws.RespectBinding;
45import javax.xml.ws.RespectBindingFeature;
46import javax.xml.ws.WebServiceException;
47import javax.xml.ws.WebServiceFeature;
48import javax.xml.ws.soap.Addressing;
49import javax.xml.ws.soap.AddressingFeature;
50import javax.xml.ws.soap.MTOM;
51import javax.xml.ws.soap.MTOMFeature;
52import javax.xml.ws.spi.WebServiceFeatureAnnotation;
53
54import com.oracle.webservices.internal.api.EnvelopeStyleFeature;
55
56import java.lang.annotation.Annotation;
57import java.lang.reflect.InvocationTargetException;
58import java.lang.reflect.Method;
59import java.lang.reflect.Constructor;
60import java.util.*;
61import java.util.logging.Level;
62import java.util.logging.Logger;
63
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 */
70public 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    }
79
80    private Map<Class<? extends WebServiceFeature>, WebServiceFeature> wsfeatures = new HashMap<Class<? extends WebServiceFeature>, WebServiceFeature>();
81    private boolean isValidating = false;
82
83    public WebServiceFeatureList() {
84    }
85
86    /**
87     * Delegate to this parent if non-null.
88     */
89    private @Nullable
90        WSDLFeaturedObject parent;
91
92    public WebServiceFeatureList(@NotNull WebServiceFeature... features) {
93        if (features != null) {
94            for (WebServiceFeature f : features) {
95                addNoValidate(f);
96            }
97        }
98    }
99
100    public void validate() {
101        if (!isValidating) {
102            isValidating = true;
103
104            // validation
105            for (WebServiceFeature ff : this) {
106                validate(ff);
107            }
108        }
109    }
110
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    }
126
127    public WebServiceFeatureList(WebServiceFeatureList features) {
128        if (features != null) {
129            wsfeatures.putAll(features.wsfeatures);
130            parent = features.parent;
131            isValidating = features.isValidating;
132        }
133    }
134
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    }
141
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    }
156
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    }
187
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    }
209
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    }
219
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;
224
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        }
252
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    }
265
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");
274
275            for (Method builderMethod : builder.getClass().getDeclaredMethods()) {
276                if (!builderMethod.equals(buildMethod) && !builderMethod.isSynthetic()) {
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            }
287
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            LOGGER.log(Level.INFO, "Unable to find builder method on webservice feature: " + beanClass.getName(), e);
296            return null;
297        } catch (final IllegalAccessException e) {
298            throw new WebServiceException(e);
299        } catch (final InvocationTargetException e) {
300            throw new WebServiceException(e);
301        }
302    }
303
304    // TODO this will be removed after package move is complete.
305    private static boolean skipDuringOrgJvnetWsToComOracleWebservicesPackageMove(
306        final Method builderMethod,
307        final Object annotationFieldValue)
308    {
309        final Class<?> annotationFieldValueClass = annotationFieldValue.getClass();
310        if (! annotationFieldValueClass.isEnum()) {
311            return false;
312        }
313        final Class<?>[] builderMethodParameterTypes = builderMethod.getParameterTypes();
314        if (builderMethodParameterTypes.length != 1) {
315            throw new WebServiceException("expected only 1 parameter");
316        }
317        final String builderParameterTypeName = builderMethodParameterTypes[0].getName();
318        if (! builderParameterTypeName.startsWith("com.oracle.webservices.internal.test.features_annotations_enums.apinew") &&
319            ! builderParameterTypeName.startsWith("com.oracle.webservices.internal.api")) {
320            return false;
321        }
322        return false;
323    }
324
325    public Iterator<WebServiceFeature> iterator() {
326        if (parent != null)
327            return new MergedFeatures(parent.getFeatures());
328        return wsfeatures.values().iterator();
329    }
330
331    public @NotNull
332        WebServiceFeature[] toArray() {
333        if (parent != null)
334            return new MergedFeatures(parent.getFeatures()).toArray();
335        return wsfeatures.values().toArray(new WebServiceFeature[] {});
336    }
337
338    public boolean isEnabled(@NotNull Class<? extends WebServiceFeature> feature) {
339        WebServiceFeature ftr = get(feature);
340        return ftr != null && ftr.isEnabled();
341    }
342
343    public boolean contains(@NotNull Class<? extends WebServiceFeature> feature) {
344        WebServiceFeature ftr = get(feature);
345        return ftr != null;
346    }
347
348    public @Nullable
349        <F extends WebServiceFeature> F get(@NotNull Class<F> featureType) {
350        WebServiceFeature f = featureType.cast(wsfeatures.get(featureType));
351        if (f == null && parent != null) {
352            return parent.getFeatures().get(featureType);
353        }
354        return (F) f;
355    }
356
357    /**
358     * Adds a feature to the list if it's not already added.
359     */
360    public void add(@NotNull WebServiceFeature f) {
361        if(addNoValidate(f) && isValidating)
362            validate(f);
363    }
364
365    private boolean addNoValidate(@NotNull WebServiceFeature f) {
366        if (!wsfeatures.containsKey(f.getClass())) {
367            wsfeatures.put(f.getClass(), f);
368
369            if (f instanceof ImpliesWebServiceFeature)
370                ((ImpliesWebServiceFeature) f).implyFeatures(this);
371
372            return true;
373        }
374
375        return false;
376    }
377
378    /**
379     * Adds features to the list if it's not already added.
380     */
381    public void addAll(@NotNull Iterable<WebServiceFeature> list) {
382        for (WebServiceFeature f : list)
383            add(f);
384    }
385
386    /**
387     * Sets MTOM feature, overriding any existing feature.  This is necessary for compatibility
388     * with the existing {@link SOAPBinding.setMTOMEnabled}.
389     * @param b if MTOM will be enabled
390     */
391    void setMTOMEnabled(boolean b) {
392        wsfeatures.put(MTOMFeature.class, new MTOMFeature(b));
393    }
394
395    public boolean equals(Object other) {
396        if (!(other instanceof WebServiceFeatureList))
397            return false;
398
399        WebServiceFeatureList w = (WebServiceFeatureList) other;
400        return wsfeatures.equals(w.wsfeatures) && (parent == w.parent);
401    }
402
403    public String toString() {
404        return wsfeatures.toString();
405    }
406
407    /**
408     * Merges the extra features that are not already set on binding.
409     * i.e, if a feature is set already on binding through some other API
410     * the corresponding wsdlFeature is not set.
411     *
412     * @param features          Web Service features that need to be merged with already configured features.
413     * @param reportConflicts   If true, checks if the feature setting in WSDL (wsdl extension or
414     *                          policy configuration) conflicts with feature setting in Deployed Service and
415     *                          logs warning if there are any conflicts.
416     */
417    public void mergeFeatures(@NotNull Iterable<WebServiceFeature> features, boolean reportConflicts) {
418        for (WebServiceFeature wsdlFtr : features) {
419            if (get(wsdlFtr.getClass()) == null) {
420                add(wsdlFtr);
421            } else if (reportConflicts) {
422                if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
423                    LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
424                                       get(wsdlFtr.getClass()), wsdlFtr));
425                }
426            }
427        }
428    }
429
430    public void mergeFeatures(WebServiceFeature[] features, boolean reportConflicts) {
431        for (WebServiceFeature wsdlFtr : features) {
432            if (get(wsdlFtr.getClass()) == null) {
433                add(wsdlFtr);
434            } else if (reportConflicts) {
435                if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
436                    LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
437                                       get(wsdlFtr.getClass()), wsdlFtr));
438                }
439            }
440        }
441    }
442
443    /**
444     * Extracts features from {@link WSDLPort#getFeatures()}. Extra features
445     * that are not already set on binding. i.e, if a feature is set already on
446     * binding through some other API the corresponding wsdlFeature is not set.
447     *
448     * @param wsdlPort
449     *            WSDLPort model
450     * @param honorWsdlRequired
451     *            If this is true add WSDL Feature only if wsd:Required=true In
452     *            SEI case, it should be false In Provider case, it should be
453     *            true
454     * @param reportConflicts
455     *            If true, checks if the feature setting in WSDL (wsdl extension
456     *            or policy configuration) conflicts with feature setting in
457     *            Deployed Service and logs warning if there are any conflicts.
458     */
459    public void mergeFeatures(@NotNull WSDLPort wsdlPort,
460                              boolean honorWsdlRequired, boolean reportConflicts) {
461        if (honorWsdlRequired && !isEnabled(RespectBindingFeature.class))
462            return;
463        if (!honorWsdlRequired) {
464            addAll(wsdlPort.getFeatures());
465            return;
466        }
467        // Add only if isRequired returns true, when honorWsdlRequired is true
468        for (WebServiceFeature wsdlFtr : wsdlPort.getFeatures()) {
469            if (get(wsdlFtr.getClass()) == null) {
470                try {
471                    // if it is a WSDL Extension , it will have required
472                    // attribute
473                    Method m = (wsdlFtr.getClass().getMethod("isRequired"));
474                    try {
475                        boolean required = (Boolean) m.invoke(wsdlFtr);
476                        if (required)
477                            add(wsdlFtr);
478                    } catch (IllegalAccessException e) {
479                        throw new WebServiceException(e);
480                    } catch (InvocationTargetException e) {
481                        throw new WebServiceException(e);
482                    }
483                } catch (NoSuchMethodException e) {
484                    // this wsdlFtr is not an WSDL extension, just add it
485                    add(wsdlFtr);
486                }
487            } else if (reportConflicts) {
488                if (isEnabled(wsdlFtr.getClass()) != wsdlFtr.isEnabled()) {
489                    LOGGER.warning(ModelerMessages.RUNTIME_MODELER_FEATURE_CONFLICT(
490                                       get(wsdlFtr.getClass()), wsdlFtr));
491                }
492
493            }
494        }
495    }
496
497    /**
498     * Set the parent features. Basically the parent feature list will be
499     * overriden by this feature list.
500     */
501    public void setParentFeaturedObject(@NotNull WSDLFeaturedObject parent) {
502        this.parent = parent;
503    }
504
505    public static @Nullable <F extends WebServiceFeature> F getFeature(@NotNull WebServiceFeature[] features,
506                                                                       @NotNull Class<F> featureType) {
507        for (WebServiceFeature f : features) {
508            if (f.getClass() == featureType)
509                return (F) f;
510        }
511        return null;
512    }
513
514    /**
515     * A Union of this WebServiceFeatureList and the parent.
516     */
517    private final class MergedFeatures implements Iterator<WebServiceFeature> {
518        private final Stack<WebServiceFeature> features = new Stack<WebServiceFeature>();
519
520        public MergedFeatures(@NotNull WSFeatureList parent) {
521
522            for (WebServiceFeature f : wsfeatures.values()) {
523                features.push(f);
524            }
525
526            for (WebServiceFeature f : parent) {
527                if (!wsfeatures.containsKey(f.getClass())) {
528                    features.push(f);
529                }
530            }
531        }
532
533        public boolean hasNext() {
534            return !features.empty();
535        }
536
537        public WebServiceFeature next() {
538            if (!features.empty()) {
539                return features.pop();
540            }
541            throw new NoSuchElementException();
542        }
543
544        public void remove() {
545            if (!features.empty()) {
546                features.pop();
547            }
548        }
549
550        public WebServiceFeature[] toArray() {
551            return features.toArray(new WebServiceFeature[] {});
552        }
553    }
554
555    private static final Logger LOGGER = Logger.getLogger(WebServiceFeatureList.class.getName());
556
557    @Override
558    public Set<java.util.Map.Entry<Class<? extends WebServiceFeature>, WebServiceFeature>> entrySet() {
559        return wsfeatures.entrySet();
560    }
561
562    @Override
563    public WebServiceFeature put(Class<? extends WebServiceFeature> key, WebServiceFeature value) {
564        return wsfeatures.put(key, value);
565    }
566
567    static public SOAPVersion getSoapVersion(WSFeatureList features) {
568        {
569            EnvelopeStyleFeature env = features.get(EnvelopeStyleFeature.class);
570            if (env != null) {
571                return SOAPVersion.from(env);
572            }
573        }
574        com.oracle.webservices.internal.api.EnvelopeStyleFeature env = features.get(com.oracle.webservices.internal.api.EnvelopeStyleFeature.class);
575        return env != null ? SOAPVersion.from(env) : null;
576    }
577
578    static public boolean isFeatureEnabled(Class<? extends WebServiceFeature> type, WebServiceFeature[] features) {
579        WebServiceFeature ftr = getFeature(features, type);
580        return ftr != null && ftr.isEnabled();
581    }
582
583    static public WebServiceFeature[] toFeatureArray(WSBinding binding) {
584        //TODO scchen convert BindingID  to WebServiceFeature[]
585        if(!binding.isFeatureEnabled(EnvelopeStyleFeature.class)) {
586            WebServiceFeature[] f = { binding.getSOAPVersion().toFeature() };
587            binding.getFeatures().mergeFeatures(f, false);
588        }
589        return binding.getFeatures().toArray();
590    }
591}
592