# NativeQueryLookupStrategy.java

    package org.codefilarete.stalactite.spring.repository.query.nativ;

    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;

    import org.codefilarete.stalactite.engine.runtime.AdvancedEntityPersister;
    import org.codefilarete.stalactite.spring.repository.query.NativeQueries;
    import org.codefilarete.stalactite.spring.repository.query.NativeQuery;
    import org.codefilarete.stalactite.spring.repository.query.StalactiteQueryMethod;
    import org.codefilarete.stalactite.sql.ConnectionProvider;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.Strings;
    import org.codefilarete.tool.VisibleForTesting;
    import org.springframework.data.projection.ProjectionFactory;
    import org.springframework.data.repository.core.NamedQueries;
    import org.springframework.data.repository.core.RepositoryMetadata;
    import org.springframework.data.repository.query.QueryLookupStrategy;
    import org.springframework.data.repository.query.RepositoryQuery;

    import static org.codefilarete.stalactite.sql.ServiceLoaderDialectResolver.DatabaseSignet;

    /**
     * {@link QueryLookupStrategy} that tries to detect a query declared via {@link NativeQuery} annotation.
     *
     * @author Guillaume Mary
     */
    public class NativeQueryLookupStrategy<C> implements QueryLookupStrategy {

    	private final Dialect dialect;
    	private final ConnectionProvider connectionProvider;
    	private final AdvancedEntityPersister<C, ?> entityPersister;

    	/**
    	 * Creates a new {@link NativeQueryLookupStrategy}.
    	 *
    	 */
    	public NativeQueryLookupStrategy(AdvancedEntityPersister<C, ?> entityPersister,
    									 Dialect dialect,
    									 ConnectionProvider connectionProvider) {
    		this.entityPersister = entityPersister;
    		this.dialect = dialect;
    		this.connectionProvider = connectionProvider;
    	}

    	/**
    	 * @return null if no declared query is found on the method through the {@link NativeQuery} annotation
    	 */
    	@Override
    	public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) {
    		NativeQuery nativeQueryAnnotation = findSQL(method);
    		if (nativeQueryAnnotation != null) {
    			String sql = nativeQueryAnnotation.value();
    			String sqlCount = nativeQueryAnnotation.countQuery();
    			StalactiteQueryMethod nativeQueryMethod = new StalactiteQueryMethod(method, metadata, factory);

    			return new SqlNativeRepositoryQuery<>(
    					nativeQueryMethod,
    					sql,
    					sqlCount,
    					entityPersister,
    					factory,
    					dialect,
    					connectionProvider);
    		} else {
    			return null;
    		}
    	}

    	@VisibleForTesting
    	@javax.annotation.Nullable
    	NativeQuery findSQL(Method method) {
    		Nullable<List<NativeQuery>> queries = Nullable.nullable(method.getAnnotation(NativeQueries.class)).map(NativeQueries::value).map(Arrays::asList);
    		if (queries.isPresent()) {
    			// Several @NativeQuery found, we look up for the best that suits Dialect compatibility
    			TreeMap<DatabaseSignet, NativeQuery> dialectPerSortedCompatibility = new TreeMap<>(DatabaseSignet.COMPARATOR);
    			queries.get().forEach(query -> dialectPerSortedCompatibility.merge(new DatabaseSignet(query.vendor(), query.major(), query.minor()), query, (c1, c2) -> {
    				// we use same properties as DatabaseSignet comparator ones since we use a TreeMap based on it
    				String printableSignet = Strings.footPrint(new DatabaseSignet(c1.vendor(), c1.major(), c1.minor()), DatabaseSignet::toString);
    				throw new IllegalStateException("Multiple queries with same database compatibility found on method " + Reflections.toString(method) + " : " + printableSignet);
    			}));

    			DatabaseSignet currentSignet = dialect.getCompatibility();
    			// we select the highest query among the smaller than database version
    			Map.Entry<DatabaseSignet, NativeQuery> foundEntry = dialectPerSortedCompatibility.floorEntry(currentSignet);
    			return Nullable.nullable(foundEntry).map(Map.Entry::getValue).get();
    		} else {
    			Nullable<NativeQuery> queryAnnotation = Nullable.nullable(method.getAnnotation(NativeQuery.class));
    			if (queryAnnotation.isPresent()) {
    				// we check a Dialect compatibility to let user override one particular database
    				NativeQuery nativeQuery = queryAnnotation.get();
    				int comparison = DatabaseSignet.COMPARATOR.compare(dialect.getCompatibility(), new DatabaseSignet(nativeQuery.vendor(), nativeQuery.major(), nativeQuery.minor()));
    				if (comparison >= 0) {
    					return nativeQuery;
    				}
    			}
    		}
    		return null;
    	}
    }