/******************************************************************************* | |
* Copyright (c) 2009 by SAP AG, Walldorf. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* SAP AG - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider; | |
import java.util.Collection; | |
import org.eclipse.jdt.core.Flags; | |
import org.eclipse.jdt.core.IField; | |
import org.eclipse.jdt.core.IMethod; | |
import org.eclipse.jdt.core.IType; | |
import org.eclipse.jdt.core.ITypeHierarchy; | |
import org.eclipse.jdt.core.JavaModelException; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.GeneralTypesNames; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.PrimitiveTypeHandler; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.TypeResolver; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.internal.validation.provider.TypeAdapter; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.internal.validation.provider.TypeFactory; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider.exceptions.AbstractClassNotImplementedException; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider.exceptions.InadmissableTypeException; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider.exceptions.InterfacesNotSupportedException; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider.exceptions.NoDefaultConstructorException; | |
import org.eclipse.jst.ws.jaxws.dom.runtime.validation.provider.exceptions.TypeNotFoundException; | |
import org.eclipse.jst.ws.jaxws.utils.annotations.AnnotationFactory; | |
import org.eclipse.jst.ws.jaxws.utils.annotations.IAnnotation; | |
import org.eclipse.jst.ws.jaxws.utils.annotations.IAnnotationInspector; | |
/** | |
* Provides type validation for class intedent to be used as an runtime classes in WebService.Runtime classes should fulfil following requirements:<br> | |
* <ul> | |
* 1. Should provide default constructor<br> | |
* 2. Should not implement java.rmi.Remote<br> | |
* 3. If it is a java.* type should be one of allowed java types<br> | |
* 4. Class should not contain inner classes that are not public static.<br> | |
* 5. Should not have 'multy inheritance'. This means if class extends class other than java.lang.Object it should not implement interface. In oposite | |
* if the class extends java.lang.Object it should implement only one interface. Following interfaces are not checked:<br> | |
* <br> | |
* | |
* <ul> | |
* java.lang.Comparable<br> | |
* java.lang.Clonable<br> | |
* java.io.Serializable | |
* </ul> | |
* </ul> | |
* | |
* @author Georgi Vachkov | |
*/ | |
public class RuntimeTypeValidator extends TypeValidator | |
{ | |
private static final String XML_TRANSIENT_ANNOTATION = "javax.xml.bind.annotation.XmlTransient"; //$NON-NLS-1$ | |
private static final String XML_TRANSIENT_ANNOTATION_SHORT = "XmlTransient"; //$NON-NLS-1$ | |
private static final String XML_TYPE_ANNOTATION = "javax.xml.bind.annotation.XmlType"; //$NON-NLS-1$ | |
private static final String WEB_FAULT_ANNOTATION = "javax.xml.ws.WebFault"; //$NON-NLS-1$ | |
private static final String GET = "get"; //$NON-NLS-1$ | |
private static final String OBJECT_FACTORY = "ObjectFactory"; //$NON-NLS-1$ | |
// private static final String SET = "set"; | |
private IAnnotationInspector annotationInspector = null; | |
/** | |
* Provides type validation for class intedent to be used as an runtime classes in WebService.Runtime classes should fulfil following | |
* requirements:<br> | |
* <ul> | |
* 1. Should provide default constructor<br> | |
* 2. Should not implement java.rmi.Remote<br> | |
* 3. If it is a java.* type should be one of allowed java types<br> | |
* 4. Class should not contain inner classes that are not public static.<br> | |
* 5. Should not have 'multy inheritance'. This means if class extends class other than java.lang.Object it should not implement interface. In | |
* oposite if the class extends java.lang.Object it should implement only one interface. Following interfaces are not checked:<br> | |
* <br> | |
* | |
* <ul> | |
* java.lang.Comparable<br> | |
* java.lang.Clonable<br> | |
* java.io.Serializable | |
* </ul> | |
* </ul> | |
* | |
* @param type - | |
* checked type | |
* @throws JavaModelException | |
* @throws InadmissableTypeException | |
* @see TypeValidator for list of allowed java types. | |
*/ | |
@Override | |
protected void doValidate(TypeAdapter type) throws InadmissableTypeException, JavaModelException | |
{ | |
final String typeName = type.getQualifiedName(); | |
// check for types from java.* packages | |
if (isJavaType(typeName)) | |
{ | |
if (isAllowedJavaType(typeName)) | |
{ | |
return; | |
} | |
} | |
if (Flags.isEnum(type.getType().getFlags())) | |
{ | |
return; | |
} | |
if (isXmlAnnotated(type.getType())) | |
{ | |
return; | |
} | |
if (hasObjectFactoryMethod(type.getType())) { | |
return; | |
} | |
if (type.isInterface()) | |
{ | |
throw new InterfacesNotSupportedException(typeName); | |
} | |
if (isExtendingCollectionOrMap(type)) | |
{ | |
return; | |
} | |
if (!type.hasDefaultConstructor()) | |
{ | |
throw new NoDefaultConstructorException(typeName); | |
} | |
checkRemoteNotImplemented(type); | |
checkMultipleInheritance(type); | |
checkForInadmissableInnerClasses(type); | |
checkAbstractNotImplemented(type); | |
checkPublicAttributes(type); | |
checkTypesUsedInProperties(type); | |
} | |
private boolean hasObjectFactoryMethod(final IType type) throws JavaModelException | |
{ | |
if (type.getJavaProject()==null) { | |
return false; | |
} | |
final String pack = type.getPackageFragment().getElementName(); | |
final String objectFactoryFQName = pack.length()==0 ? OBJECT_FACTORY : pack + '.' + OBJECT_FACTORY; | |
final IType objectFactory = type.getJavaProject().findType(objectFactoryFQName); | |
if (objectFactory == null) { | |
return false; | |
} | |
if (containsCreateMethod(objectFactory, type)) { | |
return true; | |
} | |
return false; | |
} | |
private boolean containsCreateMethod(final IType objectFactory, final IType type) throws JavaModelException | |
{ | |
for (IMethod method : objectFactory.getMethods()) | |
{ | |
if (method.isConstructor()) { | |
continue; | |
} | |
for (String name : TypeResolver.resolveTypes(method.getReturnType(), type)) { | |
if (type.getFullyQualifiedName().equals(name)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* Checks if <code>type</code> is annotated with annotation affecting mapping to XML. | |
* Always returns false in case <code>type</code> is binary class. | |
* | |
* @param type | |
* @return | |
* @throws JavaModelException | |
*/ | |
private boolean isXmlAnnotated(IType type) throws JavaModelException | |
{ | |
if (type.isBinary()) { | |
return false; | |
} | |
annotationInspector = AnnotationFactory.createAnnotationInspector(type); | |
IAnnotation<IType> typeAnnotation = annotationInspector.inspectType(XML_TYPE_ANNOTATION); | |
if (typeAnnotation!=null) { | |
return true; | |
} | |
typeAnnotation = annotationInspector.inspectType(WEB_FAULT_ANNOTATION); | |
if (typeAnnotation!=null) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Performs check on public member variables of type. 1. Checks if the type is a java API class whether it is supported. 2. If is other type | |
* perform rutime class check using validate(TypeProxy type) method. | |
* | |
* @see RuntimeTypeValidator - checkType(TypeProxy type) method. | |
* @param type | |
* @throws InadmissableTypeException | |
* @throws JavaModelException | |
*/ | |
private void checkPublicAttributes(TypeAdapter type) throws InadmissableTypeException, JavaModelException | |
{ | |
IType iType = type.getType(); | |
for (IField field : iType.getFields()) | |
{ | |
if (Flags.isPublic(field.getFlags())) | |
{ | |
resolveAndCheckType(field.getTypeSignature(), iType); | |
} | |
} | |
} | |
/** | |
* Check if the <code>type</code> implements {@link java.util.Collection} or {@link java.util.Map} | |
* | |
* @param type | |
* @return true - if implements {@link java.util.Collection} or {@link java.util.Map} | |
* @throws TypeNotFoundException | |
* @throws JavaModelException | |
*/ | |
private boolean isExtendingCollectionOrMap(TypeAdapter type) throws TypeNotFoundException, JavaModelException | |
{ | |
if (type.isImplementing(GeneralTypesNames.JAVA_UTIL_COLLECTION)) | |
{ | |
return true; | |
} | |
if (type.isImplementing(GeneralTypesNames.JAVA_UTIL_MAP)) | |
{ | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Checks types used as return value, parameter types. | |
* | |
* @param type | |
* @throws InadmissableTypeException | |
* @throws JavaModelException | |
*/ | |
private void checkTypesUsedInProperties(TypeAdapter type) throws InadmissableTypeException, JavaModelException | |
{ | |
IType iType = type.getType(); | |
for (IMethod method : iType.getMethods()) | |
{ | |
// Constructors are not validated | |
if (method.isConstructor() || isXmlTransient(method)) | |
{ | |
continue; | |
} | |
// only getters are validated | |
if (!isGetter(method)) | |
{// || !findSetter(method)) { | |
return; | |
} | |
resolveAndCheckType(method.getReturnType(), method.getDeclaringType()); | |
} | |
} | |
private boolean isGetter(final IMethod method) | |
{ | |
return method.getElementName().startsWith(GET); | |
} | |
private boolean isXmlTransient(IMethod method) throws JavaModelException | |
{ | |
if (method.getDeclaringType().isBinary()) | |
{ | |
return false; | |
} | |
annotationInspector = AnnotationFactory.createAnnotationInspector(method.getDeclaringType()); | |
Collection<IAnnotation<IMethod>> methodAnnotations = annotationInspector.inspectMethod(method); | |
boolean result = false; | |
for (IAnnotation<IMethod> methodAnnotation : methodAnnotations) | |
{ | |
if(methodAnnotation.getAnnotationName().equals(XML_TRANSIENT_ANNOTATION) || | |
methodAnnotation.getAnnotationName().equals(XML_TRANSIENT_ANNOTATION_SHORT)) | |
{ | |
result = true; | |
} | |
} | |
return result; | |
} | |
/** | |
* Provides type check with type resolving. | |
* | |
* @param typeName | |
* @param type | |
* @throws JavaModelException | |
* @throws TypeNotFoundException | |
* @throws InadmissableTypeException | |
*/ | |
private void resolveAndCheckType(String typeName, IType type) throws JavaModelException, TypeNotFoundException, InadmissableTypeException | |
{ | |
for (String resolvedType : TypeResolver.resolveTypes(typeName, type)) | |
{ | |
if (PrimitiveTypeHandler.isVoidType(resolvedType)) | |
{ | |
continue; | |
} | |
if (isJavaType(resolvedType) && isAllowedJavaType(resolvedType)) | |
{ | |
continue; | |
} | |
validateInternal(new TypeAdapter(TypeFactory.create(resolvedType, type))); | |
} | |
} | |
/** | |
* Validates if <code>type</code> is abstract class whether it is implemented by some class capable for runtime class. | |
* | |
* @param type | |
* @throws InadmissableTypeException | |
* @throws JavaModelException | |
*/ | |
private void checkAbstractNotImplemented(TypeAdapter type) throws InadmissableTypeException, JavaModelException | |
{ | |
// check for implementor for abstract class | |
if (type.isAbstract() && !isImplemented(type.getType())) | |
{ | |
throw new AbstractClassNotImplementedException(type.getQualifiedName()); | |
} | |
} | |
/** | |
* Checks whether <code>base</code> class has some non abstract implementor which is capable for runtime class. | |
* | |
* @param base | |
* checked type | |
* @throws JavaModelException | |
*/ | |
private boolean isImplemented(IType base) throws JavaModelException | |
{ | |
if (base.getFullyQualifiedName().equals(GeneralTypesNames.JAVA_LANG_OBJECT) | |
|| base.getFullyQualifiedName().equals(GeneralTypesNames.JAVA_IO_SERIALIZABLE)) | |
{ | |
return true; | |
} | |
ITypeHierarchy typeHierarchy = base.newTypeHierarchy(null); | |
IType[] classes = typeHierarchy.getAllSubtypes(base); | |
IType classCandidate = null; | |
for (int i = 0; i < classes.length; i++) | |
{ | |
classCandidate = classes[i]; | |
if (canBeUsedAsRTClass(classCandidate)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Check if given type is suitable for class/interface implementor. Class should not be abstract, interface and should comply to the rules defined | |
* in checkType documentation. | |
* | |
* @see RuntimeTypeValidator - checkType(TypeProxy type) method. | |
* @param type - | |
* checked type | |
* @throws JavaModelException | |
*/ | |
private boolean canBeUsedAsRTClass(IType type) throws JavaModelException | |
{ | |
int flags = type.getFlags(); | |
if (!type.isClass() || Flags.isAbstract(flags)) | |
{ | |
return false; | |
} | |
if (Flags.isInterface(flags) || type.isAnonymous()) | |
{ | |
return false; | |
} | |
// check sub type if it is allowed as rt classes | |
try | |
{ | |
TypeAdapter tp = new TypeAdapter(type); | |
validateInternal(tp); | |
} catch (InadmissableTypeException e) | |
{ | |
// $JL-EXC$ | |
return false; | |
} | |
return true; | |
} | |
} |