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.spi.db;
27
28import java.lang.reflect.Array;
29import java.lang.reflect.Field;
30import java.lang.reflect.GenericArrayType;
31import java.lang.reflect.Method;
32import java.lang.reflect.ParameterizedType;
33import java.lang.reflect.Type;
34import java.security.AccessController;
35import java.security.PrivilegedActionException;
36import java.security.PrivilegedExceptionAction;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42
43import javax.xml.bind.JAXBElement;
44import javax.xml.bind.annotation.XmlElement;
45import javax.xml.bind.annotation.XmlElementRef;
46import javax.xml.bind.annotation.XmlElementWrapper;
47import javax.xml.namespace.QName;
48import javax.xml.ws.WebServiceException;
49import static com.sun.xml.internal.ws.spi.db.PropertyGetterBase.verifyWrapperType;
50
51/**
52 * JAXBWrapperAccessor
53 *
54 * @author shih-chang.chen@oracle.com
55 */
56@SuppressWarnings({ "unchecked", "rawtypes" })
57public class JAXBWrapperAccessor extends WrapperAccessor {
58
59    protected Class<?> contentClass;
60    protected HashMap<Object, Class> elementDeclaredTypes;
61
62    public JAXBWrapperAccessor(Class<?> wrapperBean) {
63        verifyWrapperType(wrapperBean);
64        contentClass = (Class<?>) wrapperBean;
65
66        HashMap<Object, PropertySetter> setByQName = new HashMap<Object, PropertySetter>();
67        HashMap<Object, PropertySetter> setByLocalpart = new HashMap<Object, PropertySetter>();
68        HashMap<String, Method> publicSetters = new HashMap<String, Method>();
69
70        HashMap<Object, PropertyGetter> getByQName = new HashMap<Object, PropertyGetter>();
71        HashMap<Object, PropertyGetter> getByLocalpart = new HashMap<Object, PropertyGetter>();
72        HashMap<String, Method> publicGetters = new HashMap<String, Method>();
73
74        HashMap<Object, Class> elementDeclaredTypesByQName = new HashMap<Object, Class>();
75        HashMap<Object, Class> elementDeclaredTypesByLocalpart = new HashMap<Object, Class>();
76
77        for (Method method : contentClass.getMethods()) {
78            if (PropertySetterBase.setterPattern(method)) {
79                String key = method.getName()
80                        .substring(3, method.getName().length()).toLowerCase();
81                publicSetters.put(key, method);
82            }
83            if (PropertyGetterBase.getterPattern(method)) {
84                String methodName = method.getName();
85                String key = methodName.startsWith("is") ? methodName
86                        .substring(2, method.getName().length()).toLowerCase()
87                        : methodName.substring(3, method.getName().length())
88                                .toLowerCase();
89                publicGetters.put(key, method);
90            }
91        }
92        HashSet<String> elementLocalNames = new HashSet<String>();
93        for (Field field : getAllFields(contentClass)) {
94            XmlElementWrapper xmlElemWrapper = field.getAnnotation(XmlElementWrapper.class);
95            XmlElement xmlElem = field.getAnnotation(XmlElement.class);
96            XmlElementRef xmlElemRef = field.getAnnotation(XmlElementRef.class);
97            String fieldName = field.getName().toLowerCase();
98            String namespace = "";
99            String localName = field.getName();
100            if (xmlElemWrapper != null) {
101                namespace = xmlElemWrapper.namespace();
102                if (xmlElemWrapper.name() != null && !xmlElemWrapper.name().equals("")
103                        && !xmlElemWrapper.name().equals("##default")) {
104                    localName = xmlElemWrapper.name();
105                }
106            }else if (xmlElem != null) {
107                namespace = xmlElem.namespace();
108                if (xmlElem.name() != null && !xmlElem.name().equals("")
109                        && !xmlElem.name().equals("##default")) {
110                    localName = xmlElem.name();
111                }
112            } else if (xmlElemRef != null) {
113                namespace = xmlElemRef.namespace();
114                if (xmlElemRef.name() != null && !xmlElemRef.name().equals("")
115                        && !xmlElemRef.name().equals("##default")) {
116                    localName = xmlElemRef.name();
117                }
118            }
119            if (elementLocalNames.contains(localName)) {
120                this.elementLocalNameCollision = true;
121            } else {
122                elementLocalNames.add(localName);
123            }
124
125            QName qname = new QName(namespace, localName);
126            if (field.getType().equals(JAXBElement.class)) {
127                if (field.getGenericType() instanceof ParameterizedType) {
128                    Type arg = ((ParameterizedType) field.getGenericType())
129                            .getActualTypeArguments()[0];
130                    if (arg instanceof Class) {
131                        elementDeclaredTypesByQName.put(qname, (Class) arg);
132                        elementDeclaredTypesByLocalpart.put(localName,
133                                (Class) arg);
134                    } else if (arg instanceof GenericArrayType) {
135                        Type componentType = ((GenericArrayType) arg)
136                                .getGenericComponentType();
137                        if (componentType instanceof Class) {
138                            Class arrayClass = Array.newInstance(
139                                    (Class) componentType, 0).getClass();
140                            elementDeclaredTypesByQName.put(qname, arrayClass);
141                            elementDeclaredTypesByLocalpart.put(localName,
142                                    arrayClass);
143                        }
144                    }
145                }
146
147            }
148            Method setMethod = accessor(publicSetters, fieldName, localName);
149            Method getMethod = accessor(publicGetters, fieldName, localName);
150            if ( isProperty(field, getMethod, setMethod) ) {
151                PropertySetter setter = createPropertySetter(field, setMethod);
152                PropertyGetter getter = createPropertyGetter(field, getMethod);
153                setByQName.put(qname, setter);
154                setByLocalpart.put(localName, setter);
155                getByQName.put(qname, getter);
156                getByLocalpart.put(localName, getter);
157            }
158        }
159        if (this.elementLocalNameCollision) {
160            this.propertySetters = setByQName;
161            this.propertyGetters = getByQName;
162            elementDeclaredTypes = elementDeclaredTypesByQName;
163        } else {
164            this.propertySetters = setByLocalpart;
165            this.propertyGetters = getByLocalpart;
166            elementDeclaredTypes = elementDeclaredTypesByLocalpart;
167        }
168    }
169
170    static private Method accessor(HashMap<String, Method> map, String fieldName, String localName) {
171        Method a = map.get(fieldName);
172        if (a == null) a = map.get(localName);
173        if (a == null && fieldName.startsWith("_")) a = map.get(fieldName.substring(1));
174        return a;
175    }
176
177    static private boolean isProperty(Field field, Method getter, Method setter) {
178        if (java.lang.reflect.Modifier.isPublic(field.getModifiers())) return true;
179        if (getter == null) return false;
180        if (setter == null) {
181            return java.util.Collection.class.isAssignableFrom(field.getType()) ||
182                   java.util.Map.class.isAssignableFrom(field.getType()) ;
183        } else {
184            return true;
185        }
186    }
187
188    static private List<Field> getAllFields(Class<?> clz) {
189        List<Field> list = new ArrayList<Field>();
190        while (!Object.class.equals(clz)) {
191            list.addAll(Arrays.asList(getDeclaredFields(clz)));
192            clz = clz.getSuperclass();
193        }
194        return list;
195    }
196
197    static private Field[] getDeclaredFields(final Class<?> clz) {
198        try {
199            return AccessController.doPrivileged(new PrivilegedExceptionAction<Field[]>() {
200                        @Override
201                        public Field[] run() throws IllegalAccessException {
202                            return clz.getDeclaredFields();
203                        }
204                    });
205        } catch (PrivilegedActionException e) {
206            throw new WebServiceException(e);
207        }
208    }
209
210    static private PropertyGetter createPropertyGetter(Field field, Method getMethod) {
211        if (!field.isAccessible()) {
212            if (getMethod != null) {
213                MethodGetter methodGetter = new MethodGetter(getMethod);
214                if (methodGetter.getType().toString().equals(field.getType().toString())) {
215                    return methodGetter;
216                }
217            }
218        }
219        return new PrivFieldGetter(field);
220    }
221
222    static private PropertySetter createPropertySetter(Field field,
223            Method setter) {
224        if (!field.isAccessible()) {
225            if (setter != null) {
226                MethodSetter injection = new MethodSetter(setter);
227                if (injection.getType().toString().equals(field.getType().toString())) {
228                    return injection;
229                }
230            }
231        }
232        return new PrivFieldSetter(field);
233    }
234
235    private Class getElementDeclaredType(QName name) {
236        Object key = (this.elementLocalNameCollision) ? name : name
237                .getLocalPart();
238        return elementDeclaredTypes.get(key);
239    }
240
241    @Override
242    public PropertyAccessor getPropertyAccessor(String ns, String name) {
243        final QName n = new QName(ns, name);
244        final PropertySetter setter = getPropertySetter(n);
245        final PropertyGetter getter = getPropertyGetter(n);
246        final boolean isJAXBElement = setter.getType()
247                .equals(JAXBElement.class);
248        final boolean isListType = java.util.List.class.isAssignableFrom(setter
249                .getType());
250        final Class elementDeclaredType = isJAXBElement ? getElementDeclaredType(n)
251                : null;
252        return new PropertyAccessor() {
253            @Override
254            public Object get(Object bean) throws DatabindingException {
255                Object val;
256                if (isJAXBElement) {
257                    JAXBElement<Object> jaxbElement = (JAXBElement<Object>) JAXBWrapperAccessor.get(getter, bean);
258                    val = (jaxbElement == null) ? null : jaxbElement.getValue();
259                } else {
260                    val = JAXBWrapperAccessor.get(getter, bean);
261                }
262                if (val == null && isListType) {
263                    val = new java.util.ArrayList();
264                    set(bean, val);
265                }
266                return val;
267            }
268
269            @Override
270            public void set(Object bean, Object value) throws DatabindingException {
271                if (isJAXBElement) {
272                    JAXBElement<Object> jaxbElement = new JAXBElement<Object>(
273                            n, elementDeclaredType, contentClass, value);
274                    JAXBWrapperAccessor.set(setter, bean, jaxbElement);
275                } else {
276                    JAXBWrapperAccessor.set(setter, bean, value);
277                }
278            }
279        };
280    }
281
282    static  private Object get(PropertyGetter getter, Object wrapperInstance) {
283        return (getter instanceof PrivFieldGetter)?
284            ((PrivFieldGetter)getter).getPriv(wrapperInstance):
285            getter.get(wrapperInstance);
286    }
287
288    static private void set(PropertySetter setter, Object wrapperInstance, Object value) {
289        if (setter instanceof PrivFieldSetter)
290            ((PrivFieldSetter)setter).setPriv(wrapperInstance, value);
291        else
292            setter.set(wrapperInstance, value);
293    }
294
295
296    static private class PrivFieldSetter extends FieldSetter {
297        private PrivFieldSetter(Field f) {
298            super(f);
299        }
300        private void setPriv(final Object instance, final Object val) {
301            final Object resource = (type.isPrimitive() && val == null)? uninitializedValue(type): val;
302            if (field.isAccessible()) {
303                try {
304                    field.set(instance, resource);
305                } catch (Exception e) {
306                    throw new WebServiceException(e);
307                }
308            } else {
309                try {
310                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
311                        public Object run() throws IllegalAccessException {
312                            if (!field.isAccessible()) {
313                                field.setAccessible(true);
314                            }
315                            field.set(instance, resource);
316                            return null;
317                        }
318                    });
319                } catch (PrivilegedActionException e) {
320                    throw new WebServiceException(e);
321                }
322            }
323        }
324    }
325
326    static private class PrivFieldGetter extends FieldGetter {
327        private PrivFieldGetter(Field f) {
328            super(f);
329        }
330        static private class PrivilegedGetter implements PrivilegedExceptionAction {
331            private Object value;
332            private Field  field;
333            private Object instance;
334            public PrivilegedGetter(Field field, Object instance) {
335                super();
336                this.field = field;
337                this.instance = instance;
338            }
339            public Object run() throws IllegalAccessException {
340                if (!field.isAccessible()) {
341                    field.setAccessible(true);
342                }
343                value = field.get(instance);
344                return null;
345            }
346        }
347        private Object getPriv(final Object instance) {
348            if (field.isAccessible()) {
349                try {
350                    return field.get(instance);
351                } catch (Exception e) {
352                    throw new WebServiceException(e);
353                }
354            } else {
355                PrivilegedGetter privilegedGetter = new PrivilegedGetter(field, instance);
356                try {
357                    AccessController.doPrivileged(privilegedGetter);
358                } catch (PrivilegedActionException e) {
359                    throw new WebServiceException(e);
360                }
361                return privilegedGetter.value;
362            }
363        }
364    }
365}
366