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 */
25
26package com.sun.xml.internal.ws.util;
27
28import java.lang.reflect.Field;
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.security.AccessController;
33import java.security.PrivilegedAction;
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.List;
37import java.util.concurrent.Callable;
38
39import javax.annotation.Resource;
40import javax.xml.ws.WebServiceException;
41
42/**
43 * Encapsulates which field/method the injection is done, and performs the
44 * injection.
45 */
46public abstract class InjectionPlan<T, R> {
47    /**
48     * Perform injection
49     *
50     * @param instance
51     *            Instance
52     * @param resource
53     *            Resource
54     */
55    public abstract void inject(T instance, R resource);
56
57    /**
58     * Perform injection, but resource is only generated if injection is
59     * necessary.
60     *
61     * @param instance
62     * @param resource
63     */
64    public void inject(T instance, Callable<R> resource) {
65        try {
66            inject(instance, resource.call());
67        } catch(Exception e) {
68            throw new WebServiceException(e);
69        }
70    }
71
72    /*
73     * Injects to a field.
74     */
75    public static class FieldInjectionPlan<T, R> extends
76            InjectionPlan<T, R> {
77        private final Field field;
78
79        public FieldInjectionPlan(Field field) {
80            this.field = field;
81        }
82
83        public void inject(final T instance, final R resource) {
84            AccessController.doPrivileged(new PrivilegedAction<Object>() {
85                public Object run() {
86                    try {
87                        if (!field.isAccessible()) {
88                            field.setAccessible(true);
89                        }
90                        field.set(instance, resource);
91                        return null;
92                    } catch (IllegalAccessException e) {
93                        throw new WebServiceException(e);
94                    }
95                }
96            });
97        }
98    }
99
100    /*
101     * Injects to a method.
102     */
103    public static class MethodInjectionPlan<T, R> extends
104            InjectionPlan<T, R> {
105        private final Method method;
106
107        public MethodInjectionPlan(Method method) {
108            this.method = method;
109        }
110
111        public void inject(T instance, R resource) {
112            invokeMethod(method, instance, resource);
113        }
114    }
115
116    /*
117     * Helper for invoking a method with elevated privilege.
118     */
119    private static void invokeMethod(final Method method, final Object instance, final Object... args) {
120        if(method==null)    return;
121        AccessController.doPrivileged(new PrivilegedAction<Void>() {
122            public Void run() {
123                try {
124                    if (!method.isAccessible()) {
125                        method.setAccessible(true);
126                    }
127                    method.invoke(instance,args);
128                } catch (IllegalAccessException e) {
129                    throw new WebServiceException(e);
130                } catch (InvocationTargetException e) {
131                    throw new WebServiceException(e);
132                }
133                return null;
134            }
135        });
136    }
137
138    /*
139     * Combines multiple {@link InjectionPlan}s into one.
140     */
141    private static class Compositor<T, R> extends InjectionPlan<T, R> {
142        private final Collection<InjectionPlan<T, R>> children;
143
144        public Compositor(Collection<InjectionPlan<T, R>> children) {
145            this.children = children;
146        }
147
148        public void inject(T instance, R res) {
149            for (InjectionPlan<T, R> plan : children)
150                plan.inject(instance, res);
151        }
152
153        public void inject(T instance, Callable<R> resource) {
154            if (!children.isEmpty()) {
155                super.inject(instance, resource);
156            }
157        }
158    }
159
160    /*
161     * Creates an {@link InjectionPlan} that injects the given resource type to the given class.
162     *
163     * @param isStatic
164     *      Only look for static field/method
165     *
166     */
167    public static <T,R>
168    InjectionPlan<T,R> buildInjectionPlan(Class<? extends T> clazz, Class<R> resourceType, boolean isStatic) {
169        List<InjectionPlan<T,R>> plan = new ArrayList<InjectionPlan<T,R>>();
170
171        Class<?> cl = clazz;
172        while(cl != Object.class) {
173            for(Field field: cl.getDeclaredFields()) {
174                Resource resource = field.getAnnotation(Resource.class);
175                if (resource != null) {
176                    if(isInjectionPoint(resource, field.getType(),
177                        "Incorrect type for field"+field.getName(),
178                        resourceType)) {
179
180                        if(isStatic && !Modifier.isStatic(field.getModifiers()))
181                            throw new WebServiceException("Static resource "+resourceType+" cannot be injected to non-static "+field);
182
183                        plan.add(new FieldInjectionPlan<T,R>(field));
184                    }
185                }
186            }
187            cl = cl.getSuperclass();
188        }
189
190        cl = clazz;
191        while(cl != Object.class) {
192            for(Method method : cl.getDeclaredMethods()) {
193                Resource resource = method.getAnnotation(Resource.class);
194                if (resource != null) {
195                    Class[] paramTypes = method.getParameterTypes();
196                    if (paramTypes.length != 1)
197                        throw new WebServiceException("Incorrect no of arguments for method "+method);
198                    if(isInjectionPoint(resource,paramTypes[0],
199                        "Incorrect argument types for method"+method.getName(),
200                        resourceType)) {
201
202                        if(isStatic && !Modifier.isStatic(method.getModifiers()))
203                            throw new WebServiceException("Static resource "+resourceType+" cannot be injected to non-static "+method);
204
205                        plan.add(new MethodInjectionPlan<T,R>(method));
206                    }
207                }
208            }
209            cl = cl.getSuperclass();
210        }
211
212        return new Compositor<T,R>(plan);
213    }
214
215    /*
216     * Returns true if the combination of {@link Resource} and the field/method type
217     * are consistent for {@link WebServiceContext} injection.
218     */
219    private static boolean isInjectionPoint(Resource resource, Class fieldType, String errorMessage, Class resourceType ) {
220        Class t = resource.type();
221        if (t.equals(Object.class)) {
222            return fieldType.equals(resourceType);
223        } else if (t.equals(resourceType)) {
224            if (fieldType.isAssignableFrom(resourceType)) {
225                return true;
226            } else {
227                // type compatibility error
228                throw new WebServiceException(errorMessage);
229            }
230        }
231        return false;
232    }
233}
234