| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.cxf.jaxb; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.GenericArrayType; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.lang.reflect.WildcardType; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import jakarta.xml.bind.annotation.XmlAccessType; |
| import jakarta.xml.bind.annotation.XmlAttribute; |
| import jakarta.xml.bind.annotation.XmlElement; |
| import jakarta.xml.bind.annotation.XmlRootElement; |
| import jakarta.xml.bind.annotation.XmlSeeAlso; |
| import jakarta.xml.bind.annotation.XmlTransient; |
| import jakarta.xml.bind.annotation.XmlType; |
| import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; |
| import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; |
| import javax.xml.namespace.QName; |
| |
| import org.apache.cxf.common.classloader.ClassLoaderUtils; |
| import org.apache.cxf.common.jaxb.JAXBUtils; |
| import org.apache.cxf.common.logging.LogUtils; |
| import org.apache.cxf.common.util.ASMHelper; |
| import org.apache.cxf.common.util.ASMHelper.ClassWriter; |
| import org.apache.cxf.common.util.ASMHelper.MethodVisitor; |
| import org.apache.cxf.common.util.ASMHelper.Opcodes; |
| import org.apache.cxf.common.util.ReflectionUtil; |
| import org.apache.cxf.common.util.StringUtils; |
| import org.apache.cxf.service.ServiceModelVisitor; |
| import org.apache.cxf.service.model.MessageInfo; |
| import org.apache.cxf.service.model.MessagePartInfo; |
| import org.apache.cxf.service.model.OperationInfo; |
| import org.apache.cxf.service.model.ServiceInfo; |
| import org.apache.cxf.service.model.UnwrappedOperationInfo; |
| |
| /** |
| * Walks the service model and sets up the classes for the context. |
| */ |
| class JAXBContextInitializer extends ServiceModelVisitor { |
| private static final Logger LOG = LogUtils.getL7dLogger(JAXBContextInitializer.class); |
| private Set<Class<?>> classes; |
| private Collection<Object> typeReferences; |
| private Set<Class<?>> globalAdapters = new HashSet<>(); |
| private Map<String, Object> unmarshallerProperties; |
| |
| JAXBContextInitializer(ServiceInfo serviceInfo, |
| Set<Class<?>> classes, |
| Collection<Object> typeReferences, |
| Map<String, Object> unmarshallerProperties) { |
| super(serviceInfo); |
| this.classes = classes; |
| this.typeReferences = typeReferences; |
| this.unmarshallerProperties = unmarshallerProperties; |
| } |
| |
| @Override |
| public void begin(MessagePartInfo part) { |
| Class<?> clazz = part.getTypeClass(); |
| if (clazz == null) { |
| return; |
| } |
| |
| if (Exception.class.isAssignableFrom(clazz)) { |
| //exceptions are handled special, make sure we mark it |
| part.setProperty(JAXBDataBinding.class.getName() + ".CUSTOM_EXCEPTION", |
| Boolean.TRUE); |
| } |
| boolean isFromWrapper = part.getMessageInfo().getOperation().isUnwrapped(); |
| if (isFromWrapper |
| && !Boolean.TRUE.equals(part.getProperty("messagepart.isheader"))) { |
| UnwrappedOperationInfo uop = (UnwrappedOperationInfo)part.getMessageInfo().getOperation(); |
| OperationInfo op = uop.getWrappedOperation(); |
| MessageInfo inf = null; |
| if (uop.getInput() == part.getMessageInfo()) { |
| inf = op.getInput(); |
| } else if (uop.getOutput() == part.getMessageInfo()) { |
| inf = op.getOutput(); |
| } |
| if (inf != null |
| && inf.getFirstMessagePart().getTypeClass() != null) { |
| //if the wrapper has a type class, we don't need to do anything |
| //as everything would have been discovered when walking the |
| //wrapper type (unless it's a header which wouldn't be in the wrapper) |
| return; |
| } |
| } |
| if (isFromWrapper |
| && clazz.isArray() |
| && !Byte.TYPE.equals(clazz.getComponentType())) { |
| clazz = clazz.getComponentType(); |
| } |
| |
| Annotation[] a = (Annotation[])part.getProperty("parameter.annotations"); |
| checkForAdapter(clazz, a); |
| |
| Type genericType = (Type) part.getProperty("generic.type"); |
| if (genericType != null) { |
| boolean isList = Collection.class.isAssignableFrom(clazz); |
| if (isFromWrapper) { |
| if (genericType instanceof Class |
| && ((Class<?>)genericType).isArray()) { |
| Class<?> cl2 = (Class<?>)genericType; |
| if (cl2.isArray() |
| && !Byte.TYPE.equals(cl2.getComponentType())) { |
| genericType = cl2.getComponentType(); |
| } |
| addType(genericType); |
| } else if (!isList) { |
| addType(genericType); |
| } |
| } else { |
| addType(genericType, true); |
| } |
| |
| if (isList |
| && genericType instanceof ParameterizedType) { |
| ParameterizedType pt = (ParameterizedType) genericType; |
| if (pt.getActualTypeArguments().length > 0 |
| && pt.getActualTypeArguments()[0] instanceof Class) { |
| |
| Class<? extends Object> arrayCls = |
| Array.newInstance((Class<?>) pt.getActualTypeArguments()[0], 0).getClass(); |
| clazz = arrayCls; |
| part.setTypeClass(clazz); |
| if (isFromWrapper) { |
| addType(clazz.getComponentType(), true); |
| } |
| } else if (pt.getActualTypeArguments().length > 0 |
| && pt.getActualTypeArguments()[0] instanceof GenericArrayType) { |
| GenericArrayType gat = (GenericArrayType)pt.getActualTypeArguments()[0]; |
| gat.getGenericComponentType(); |
| Class<? extends Object> arrayCls = |
| Array.newInstance((Class<?>) gat.getGenericComponentType(), 0).getClass(); |
| clazz = Array.newInstance(arrayCls, 0).getClass(); |
| part.setTypeClass(clazz); |
| if (isFromWrapper) { |
| addType(clazz.getComponentType(), true); |
| } |
| } |
| } |
| if (isFromWrapper && isList) { |
| clazz = null; |
| } |
| } |
| if (clazz != null) { |
| if (!isFromWrapper |
| && clazz.getAnnotation(XmlRootElement.class) == null |
| && clazz.getAnnotation(XmlType.class) != null |
| && StringUtils.isEmpty(clazz.getAnnotation(XmlType.class).name())) { |
| Object ref = createTypeReference(part.getName(), clazz); |
| if (ref != null) { |
| typeReferences.add(ref); |
| } |
| } |
| |
| addClass(clazz); |
| } |
| } |
| |
| private void checkForAdapter(Class<?> clazz, Annotation[] anns) { |
| if (anns != null) { |
| for (Annotation a : anns) { |
| if (XmlJavaTypeAdapter.class.isAssignableFrom(a.annotationType())) { |
| Type t = Utils.getTypeFromXmlAdapter((XmlJavaTypeAdapter)a); |
| if (t != null) { |
| addType(t); |
| } |
| } |
| } |
| } |
| XmlJavaTypeAdapter xjta = clazz.getAnnotation(XmlJavaTypeAdapter.class); |
| if (xjta != null) { |
| Type t = Utils.getTypeFromXmlAdapter(xjta); |
| if (t != null) { |
| addType(t); |
| } |
| } |
| if (clazz.getPackage() != null) { |
| XmlJavaTypeAdapters adapt = clazz.getPackage().getAnnotation(XmlJavaTypeAdapters.class); |
| if (adapt != null) { |
| for (XmlJavaTypeAdapter a : adapt.value()) { |
| globalAdapters.add(a.type()); |
| } |
| for (XmlJavaTypeAdapter a : adapt.value()) { |
| Type t = Utils.getTypeFromXmlAdapter(a); |
| if (t != null) { |
| addType(t); |
| } |
| } |
| } |
| } |
| } |
| |
| private void addType(Type cls) { |
| addType(cls, false); |
| } |
| private void addType(Type cls, boolean allowArray) { |
| if (cls instanceof Class) { |
| if (globalAdapters.contains(cls)) { |
| return; |
| } |
| if (((Class<?>)cls).isArray() && !allowArray) { |
| addClass(((Class<?>)cls).getComponentType()); |
| } else { |
| addClass((Class<?>)cls); |
| } |
| } else if (cls instanceof ParameterizedType) { |
| final ParameterizedType parameterizedType = (ParameterizedType)cls; |
| addType(parameterizedType.getRawType()); |
| if (!parameterizedType.getRawType().equals(Enum.class)) { |
| for (Type t2 : parameterizedType.getActualTypeArguments()) { |
| if (shouldTypeBeAdded(t2, parameterizedType)) { |
| addType(t2); |
| } |
| } |
| } |
| } else if (cls instanceof GenericArrayType) { |
| Class<?> ct; |
| GenericArrayType gt = (GenericArrayType)cls; |
| Type componentType = gt.getGenericComponentType(); |
| if (componentType instanceof Class) { |
| ct = (Class<?>)componentType; |
| } else if (componentType instanceof ParameterizedType) { |
| final ParameterizedType parameterizedType = (ParameterizedType)componentType; |
| final Type rawType = parameterizedType.getRawType(); |
| if (rawType instanceof Class) { |
| ct = (Class<?>)rawType; |
| } else { |
| throw new IllegalArgumentException("Unable to determine type for " + rawType); |
| } |
| if (!parameterizedType.getRawType().equals(Enum.class)) { |
| for (Type t2 : parameterizedType.getActualTypeArguments()) { |
| if (shouldTypeBeAdded(t2, parameterizedType)) { |
| addType(t2); |
| } |
| } |
| } |
| } else { |
| TypeVariable<?> tv = (TypeVariable<?>)componentType; |
| Type[] bounds = tv.getBounds(); |
| if (bounds != null && bounds.length == 1) { |
| if (bounds[0] instanceof Class) { |
| ct = (Class<?>)bounds[0]; |
| } else { |
| throw new IllegalArgumentException("Unable to determine type for: " + tv); |
| } |
| } else { |
| throw new IllegalArgumentException("Unable to determine type for: " + tv); |
| } |
| } |
| ct = Array.newInstance(ct, 0).getClass(); |
| |
| addClass(ct); |
| } else if (cls instanceof WildcardType) { |
| for (Type t : ((WildcardType)cls).getUpperBounds()) { |
| addType(t); |
| } |
| for (Type t : ((WildcardType)cls).getLowerBounds()) { |
| addType(t); |
| } |
| } else if (cls instanceof TypeVariable) { |
| for (Type t : ((TypeVariable<?>)cls).getBounds()) { |
| addType(t); |
| } |
| } |
| } |
| |
| private boolean shouldTypeBeAdded(final Type t2, final ParameterizedType parameterizedType) { |
| if (!(t2 instanceof TypeVariable)) { |
| return true; |
| } |
| TypeVariable<?> typeVariable = (TypeVariable<?>) t2; |
| final Type[] bounds = typeVariable.getBounds(); |
| for (Type bound : bounds) { |
| if (bound instanceof ParameterizedType && bound.equals(parameterizedType)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void addClass(Class<?> claz) { |
| if (Throwable.class.isAssignableFrom(claz)) { |
| if (!Throwable.class.equals(claz) |
| && !Exception.class.equals(claz)) { |
| walkReferences(claz); |
| } |
| addClass(String.class); |
| } else if (claz.getName().startsWith("java.") |
| || claz.getName().startsWith("javax.") |
| || claz.getName().startsWith("jakarta.")) { |
| return; |
| } else { |
| Class<?> cls = JAXBUtils.getValidClass(claz); |
| if (cls == null |
| && ReflectionUtil.getDeclaredConstructors(claz).length > 0 |
| && !Modifier.isAbstract(claz.getModifiers())) { |
| if (LOG.isLoggable(Level.INFO)) { |
| LOG.info("Class " + claz.getName() + " does not have a default constructor which JAXB requires."); |
| } |
| //there is no init(), but other constructors |
| Object factory = createFactory(claz, ReflectionUtil.getDeclaredConstructors(claz)[0]); |
| unmarshallerProperties.put("com.sun.xml.bind.ObjectFactory", factory); |
| cls = claz; |
| } |
| if (null != cls) { |
| if (classes.contains(cls)) { |
| return; |
| } |
| |
| if (!cls.isInterface()) { |
| classes.add(cls); |
| } |
| |
| XmlSeeAlso xsa = cls.getAnnotation(XmlSeeAlso.class); |
| if (xsa != null) { |
| for (Class<?> c : xsa.value()) { |
| addClass(c); |
| } |
| } |
| XmlJavaTypeAdapter xjta = cls.getAnnotation(XmlJavaTypeAdapter.class); |
| if (xjta != null) { |
| //has an adapter. We need to inspect the adapter and then |
| //return as the adapter will handle the superclass |
| //and interfaces and such |
| Type t = Utils.getTypeFromXmlAdapter(xjta); |
| if (t != null) { |
| addType(t); |
| } |
| return; |
| } |
| |
| if (cls.getSuperclass() != null) { |
| //JAXB should do this, but it doesn't always. |
| //in particular, older versions of jaxb don't |
| addClass(cls.getSuperclass()); |
| } |
| |
| if (!cls.isInterface()) { |
| walkReferences(cls); |
| } |
| } |
| } |
| } |
| |
| private void walkReferences(Class<?> cls) { |
| if (cls == null) { |
| return; |
| } |
| if (cls.getName().startsWith("java.") |
| || cls.getName().startsWith("javax.") |
| || cls.getName().startsWith("jakarta.")) { |
| return; |
| } |
| //walk the public fields/methods to try and find all the classes. JAXB will only load the |
| //EXACT classes in the fields/methods if they are in a different package. Thus, |
| //subclasses won't be found and the xsi:type stuff won't work at all. |
| //We'll grab the public field/method types and then add the ObjectFactory stuff |
| //as well as look for jaxb.index files in those packages. |
| |
| XmlAccessType accessType = Utils.getXmlAccessType(cls); |
| |
| if (accessType != XmlAccessType.PROPERTY) { // only look for fields if we are instructed to |
| //fields are accessible even if not public, must look at the declared fields |
| //then walk to parents declared fields, etc... |
| Field[] fields = ReflectionUtil.getDeclaredFields(cls); |
| for (Field f : fields) { |
| if (isFieldAccepted(f, accessType)) { |
| XmlJavaTypeAdapter xjta = Utils.getFieldXJTA(f); |
| if (xjta != null) { |
| Type t = Utils.getTypeFromXmlAdapter(xjta); |
| if (t != null) { |
| addType(t); |
| continue; |
| } |
| } |
| addType(f.getGenericType()); |
| } |
| } |
| walkReferences(cls.getSuperclass()); |
| } |
| |
| if (accessType != XmlAccessType.FIELD) { // only look for methods if we are instructed to |
| Method[] methods = ReflectionUtil.getDeclaredMethods(cls); |
| for (Method m : methods) { |
| if (isMethodAccepted(m, accessType)) { |
| XmlJavaTypeAdapter xjta = Utils.getMethodXJTA(m); |
| if (xjta != null) { |
| Type t = Utils.getTypeFromXmlAdapter(xjta); |
| if (t != null) { |
| addType(t); |
| continue; |
| } |
| } |
| addType(m.getGenericReturnType()); |
| for (Type t : m.getGenericParameterTypes()) { |
| addType(t); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks if the field is accepted as a JAXB property. |
| */ |
| static boolean isFieldAccepted(Field field, XmlAccessType accessType) { |
| // We only accept non static fields which are not marked @XmlTransient or has transient modifier |
| if (field.isAnnotationPresent(XmlTransient.class) |
| || Modifier.isTransient(field.getModifiers())) { |
| return false; |
| } |
| if (Modifier.isStatic(field.getModifiers())) { |
| return field.isAnnotationPresent(XmlAttribute.class); |
| } |
| if (accessType == XmlAccessType.PUBLIC_MEMBER |
| && !Modifier.isPublic(field.getModifiers())) { |
| return false; |
| } |
| if (accessType == XmlAccessType.NONE |
| || accessType == XmlAccessType.PROPERTY) { |
| return checkJaxbAnnotation(field.getAnnotations()); |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if the method is accepted as a JAXB property getter. |
| */ |
| static boolean isMethodAccepted(Method method, XmlAccessType accessType) { |
| // We only accept non static property getters which are not marked @XmlTransient |
| if (Modifier.isStatic(method.getModifiers()) |
| || method.isAnnotationPresent(XmlTransient.class) |
| || !Modifier.isPublic(method.getModifiers()) |
| || "getClass".equals(method.getName())) { |
| return false; |
| } |
| |
| // must not have parameters and return type must not be void |
| if (method.getReturnType() == Void.class || method.getReturnType() == Void.TYPE |
| || method.getParameterTypes().length != 0 |
| || (method.getDeclaringClass().equals(Throwable.class) |
| && !("getMessage".equals(method.getName()))) |
| || !(method.getName().startsWith("get") |
| || method.getName().startsWith("is"))) { |
| return false; |
| } |
| int beginIndex = 3; |
| if (method.getName().startsWith("is")) { |
| beginIndex = 2; |
| } |
| |
| Method setter = null; |
| try { |
| setter = method.getDeclaringClass() |
| .getMethod("set" + method.getName().substring(beginIndex), |
| new Class[] {method.getReturnType()}); |
| } catch (Exception e) { |
| //getter, but no setter |
| } |
| if (setter != null) { |
| if (setter.isAnnotationPresent(XmlTransient.class) |
| || !Modifier.isPublic(setter.getModifiers())) { |
| return false; |
| } |
| } else if (!Collection.class.isAssignableFrom(method.getReturnType()) |
| && !Throwable.class.isAssignableFrom(method.getDeclaringClass())) { |
| //no setter, it's not a collection (thus getter().add(...)), and |
| //not an Exception, |
| return false; |
| } |
| |
| if (accessType == XmlAccessType.NONE |
| || accessType == XmlAccessType.FIELD) { |
| return checkJaxbAnnotation(method.getAnnotations()); |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if there are JAXB annotations among the annotations of the class member. |
| * @param annotations the array of annotations from the class member |
| * @return true if JAXB annotations are present, false otherwise |
| */ |
| static boolean checkJaxbAnnotation(Annotation[] annotations) { |
| // must check if there are any jaxb annotations |
| Package jaxbAnnotationsPackage = XmlElement.class.getPackage(); |
| for (Annotation annotation : annotations) { |
| if (annotation.annotationType().getPackage() == jaxbAnnotationsPackage) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * The TypeReference class is a sun specific class that is found in two different |
| * locations depending on environment. In IBM JDK the class is not available at all. |
| * So we have to load it at runtime. |
| * |
| * @param n |
| * @param cls |
| * @return initiated TypeReference |
| */ |
| private static Object createTypeReference(QName n, Class<?> cls) { |
| Class<?> refClass = null; |
| try { |
| refClass = ClassLoaderUtils.loadClass("com.sun.xml.bind.api.TypeReference", |
| JAXBContextInitializer.class); |
| } catch (Throwable ex) { |
| try { |
| refClass = ClassLoaderUtils.loadClass("com.sun.xml.internal.bind.api.TypeReference", |
| JAXBContextInitializer.class); |
| } catch (Throwable ex2) { |
| //ignore |
| } |
| } |
| if (refClass != null) { |
| try { |
| return refClass.getConstructor(QName.class, Type.class, new Annotation[0].getClass()) //NOPMD |
| .newInstance(n, cls, new Annotation[0]); |
| } catch (Throwable e) { |
| //ignore |
| } |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("unused") |
| private Object createFactory(Class<?> cls, Constructor<?> contructor) { |
| String newClassName = cls.getName() + "Factory"; |
| ASMHelper helper = new ASMHelper(); |
| ClassWriter cw = helper.createClassWriter(); |
| MethodVisitor mv; |
| |
| cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, |
| ASMHelper.periodToSlashes(newClassName), null, "java/lang/Object", null); |
| |
| cw.visitSource(cls.getSimpleName() + "Factory" + ".java", null); |
| |
| mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); |
| mv.visitCode(); |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); |
| mv.visitInsn(Opcodes.RETURN); |
| mv.visitMaxs(1, 1); |
| mv.visitEnd(); |
| |
| mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "create" + cls.getSimpleName(), |
| "()L" + ASMHelper.periodToSlashes(cls.getName()) + ";", null, null); |
| mv.visitCode(); |
| String name = cls.getName().replace(".", "/"); |
| mv.visitTypeInsn(Opcodes.NEW, name); |
| mv.visitInsn(Opcodes.DUP); |
| StringBuilder paraString = new StringBuilder(32).append("("); |
| |
| for (Class<?> paraClass : contructor.getParameterTypes()) { |
| mv.visitInsn(Opcodes.ACONST_NULL); |
| paraString.append("Ljava/lang/Object;"); |
| } |
| paraString.append(")V"); |
| |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", paraString.toString(), false); |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(1, 1); |
| mv.visitEnd(); |
| |
| cw.visitEnd(); |
| Class<?> factoryClass = helper.loadClass(newClassName, cls, cw.toByteArray()); |
| try { |
| return factoryClass.newInstance(); |
| } catch (Exception e) { |
| //ignore |
| } |
| return null; |
| } |
| |
| |
| } |