/******************************************************************************* * Copyright (c) 2012, 2016 Spring IDE Developers * 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: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.data.jdt.core; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.springframework.core.GenericTypeResolver; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.core.SpringCore; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.springframework.ide.eclipse.core.model.ModelUtils; import org.springframework.ide.eclipse.data.SpringDataUtils; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Value object to easily access information about a Spring Data repository. * * @author Oliver Gierke * @author Tomasz Zarna * @author Martin Lippert */ public class RepositoryInformation { private static Set<String> METHOD_NAMES = new HashSet<String>(); static { for (Method method : PagingAndSortingRepository.class.getMethods()) { METHOD_NAMES.add(method.getName()); } } static enum Module { JPA("jpa"), MONGO("mongo"), GENERIC; public static final String BASE = "http://www.springframework.org/schema/data/"; private String namespace; private Module(String namespace) { this.namespace = StringUtils.hasText(namespace) ? BASE + namespace : ""; } private Module() { this(null); } public static Module getModuleOf(IBean bean) { for (Module candidate : values()) { String uri = ModelUtils.getNameSpaceURI(bean); if (uri != null && uri.equals(candidate.namespace)) { return candidate; } } return GENERIC; } } private final IType type; private final Class<?> repositoryInterface; private final Class<?> repositoryBaseInterface; public RepositoryInformation(IType type) { try { this.type = type; ClassLoader classLoader = JdtUtils.getClassLoader(type.getJavaProject().getProject(), null); this.repositoryInterface = classLoader.loadClass(type.getFullyQualifiedName()); this.repositoryBaseInterface = classLoader.loadClass(Repository.class.getName()); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Could not load class " + type.getFullyQualifiedName(), e); } } /** * Returns a {@link RepositoryInformation} for the repository interface of the given {@link IMethod} if it is a Spring * Data repository (meaning it supports dynamic query methods). * * @param element must not be {@literal null}. * @return */ public static RepositoryInformation create(IMethod element) { Assert.notNull(element); IType type = element.getDeclaringType(); return create(type); } /** * Returns a {@link RepositoryInformation} for the given {@link IType} if it * is a Spring Data repository (meaning it supports dynamic query methods). * * @param type * must not be {@literal null}. * @return */ public static RepositoryInformation create(IType type) { Assert.notNull(type); try { return type.isInterface() && type.exists() ? new RepositoryInformation(type) : null; } catch (JavaModelException e) { return null; } catch (IllegalArgumentException e) { return null; } } public static boolean isSpringDataRepository(IType type) { if (type == null) { return false; } try { IAnnotation[] annotations = type.getAnnotations(); for (IAnnotation annotation : annotations) { if (annotation.getElementName().equals("org.springframework.data.repository.NoRepositoryBean") || annotation.getElementName().equals("NoRepositoryBean")) { return false; } } return isSpringDataRepositoryInterfaces(type); } catch (JavaModelException e) { } return false; } public static boolean isSpringDataRepositoryInterfaces(IType type) { if (type == null) { return false; } String fullyQualifiedName = type.getFullyQualifiedName(); if ("org.springframework.data.repository.Repository".equals(fullyQualifiedName)) { return true; } /** * Spring Data Cassandra explicitly does not support dynamic query * methods, unlike most of the other Spring Data subprojects, and this * is by design. */ if ("org.springframework.data.cassandra.repository.TypedIdCassandraRepository".equals(fullyQualifiedName)) { return false; } try { IAnnotation[] annotations = type.getAnnotations(); for (IAnnotation annotation : annotations) { if (annotation.getElementName().equals("org.springframework.data.repository.RepositoryDefinition") || annotation.getElementName().equals("RepositoryDefinition")) { return true; } } String[] interfaceNames = null; if (type.isBinary()) { interfaceNames = type.getSuperInterfaceNames(); } else { interfaceNames = SpringCore.getTypeHierarchyEngine().getInterfaces(type.getJavaProject().getProject(), type.getFullyQualifiedName()); } if (interfaceNames != null) { for(String superInterface : interfaceNames) { IType interfaceType = type.getJavaProject().findType(superInterface); if (isSpringDataRepositoryInterfaces(interfaceType)) { return true; } } } } catch (JavaModelException e) { } return false; } /** * Returns the {@link KeywordProvider} to be used for this repository. * * @param project * @return */ public KeywordProvider getKeywordProvider(IJavaProject project) { TypePredicates predicates = new DefaultTypePredicates(project); IBean repositoryBean = SpringDataUtils.getRepositoryBean(project.getProject(), repositoryInterface.getName()); Module module = Module.getModuleOf(repositoryBean); switch (module) { case JPA: return new JpaKeywordProvider(predicates); case MONGO: return new MongoDbKeywordProvider(predicates); case GENERIC: default: return new KeywordProviderSupport(predicates); } } public Class<?> getManagedDomainClass() { try { Class<?>[] resolvedTypeArguments = GenericTypeResolver.resolveTypeArguments(this.repositoryInterface, this.repositoryBaseInterface); if (resolvedTypeArguments != null && resolvedTypeArguments.length > 0) { return resolvedTypeArguments[0]; } } catch (TypeNotPresentException e) { } return null; } /** * Returns all {@link IMethod}s that shall be considered query methods (which need to be validated). * * @return */ public Iterable<IMethod> getMethodsToValidate() { Set<IMethod> result = new HashSet<IMethod>(); try { for (IMethod method : type.getMethods()) { if (isMethodToValidate(method)) { result.add(method); } } } catch (JavaModelException e) { SpringCore.log(e); } return result; } public boolean isMethodToValidate(IMethod method) throws JavaModelException { if (isCrudMethod(method)) return false; if (hasAnnotation(method, "Query")) return false; if (hasAnnotation(method, "Procedure")) return false; if (Flags.isDefaultMethod(method.getFlags())) return false; String[] prefixes = new String[] {"findBy", "read", "get", "query", "count", "delete", "remove"}; String methodName = method.getElementName(); for (int i = 0; i < prefixes.length; i++) { if (methodName.startsWith(prefixes[i])) { return true; } } return false; } private boolean isCrudMethod(IMethod method) { return METHOD_NAMES.contains(method.getElementName()); } private boolean hasAnnotation(IMethod method, String annotationName) throws JavaModelException { for (IAnnotation annotation : method.getAnnotations()) { if (annotation.getElementName().equals(annotationName)) { return true; } } return false; } }