com.stehno.sjdbcx.reflection.ReflectionImplementationProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.stehno.sjdbcx.reflection.ReflectionImplementationProvider.java

Source

/*
 * Copyright (c) 2013 Christopher J. Stehno
 *
 * Licensed 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 com.stehno.sjdbcx.reflection;

import com.stehno.sjdbcx.ImplementationProvider;
import com.stehno.sjdbcx.annotation.Implemented;
import com.stehno.sjdbcx.annotation.ReplacementType;
import com.stehno.sjdbcx.annotation.Sql;
import com.stehno.sjdbcx.reflection.operation.*;
import com.stehno.sjdbcx.support.SqlResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * FIXME: document
 */
public class ReflectionImplementationProvider implements ImplementationProvider {

    private static final String INVALID_EXTEND_MSG = "Provided implementation must extend AbstractOperation.";
    private static final String MUST_IMPLEMENT_MSG = "Provided implementation must implement NamedOperation or IndexedOperation";
    private static final String ONLY_ONE_MSG = "Provided implementation must NOT implement NamedOperation AND IndexedOperation";
    private RepositoryInvocationHandler invocationHandler;
    private ApplicationContext applicationContext;
    private SqlResolver sqlResolver;
    private Class prototype;

    @Override
    public void setPrototype(final Class prototype) {
        this.prototype = prototype;
    }

    @Override
    public void init(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;

        this.sqlResolver = applicationContext.getBean(SqlResolver.class);

        invocationHandler = new RepositoryInvocationHandler();
    }

    @Override
    public void implement(final Method method) throws Exception {
        final OperationContext operationContext = applicationContext.getBean(OperationContext.class);
        final String sql = sqlResolver.resolve(prototype, method);

        final Operation operation;
        if (method.isAnnotationPresent(Implemented.class)) {
            operation = buildCustomOperation(method, sql, operationContext,
                    method.getAnnotation(Implemented.class));

        } else {
            operation = buildDefaultOperation(method, sql, operationContext, method.getAnnotation(Sql.class));
        }

        invocationHandler.addOperation(method, operation);
    }

    private Operation buildDefaultOperation(final Method method, final String sql,
            final OperationContext operationContext, final Sql sqlAnno) {
        final Operation operation;
        final boolean indexed = sqlAnno.replacement() != ReplacementType.INDEXED;

        switch (sqlAnno.type()) {
        case UPDATE:
            if (indexed) {
                operation = new NamedUpdateOperation(method, sql, operationContext);
            } else {
                operation = new IndexedUpdateOperation(method, sql, operationContext);
            }
            break;

        case QUERY:
            if (indexed) {
                operation = new NamedQueryOperation(method, sql, operationContext);
            } else {
                operation = new IndexedQueryOperation(method, sql, operationContext);
            }
            break;

        case EXECUTE:
            // TODO: add more support for execute
            operation = new NamedExecuteOperation(method, sql, operationContext);
            break;

        default:
            throw new IllegalArgumentException("Invalid SQL statement type specified.");
        }

        return operation;
    }

    private Operation buildCustomOperation(final Method method, final String sql,
            final OperationContext operationContext, final Implemented implAnno) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        final Class<? extends Operation> operationClass;
        if (StringUtils.hasText(implAnno.value())) {
            operationClass = (Class<? extends Operation>) Class.forName(implAnno.value());
        } else {
            operationClass = implAnno.type();
        }

        Assert.isAssignable(AbstractOperation.class, operationClass, INVALID_EXTEND_MSG);

        final boolean isNamed = NamedOperation.class.isAssignableFrom(operationClass);
        final boolean isIndexed = IndexedOperation.class.isAssignableFrom(operationClass);

        Assert.isTrue(isNamed || isIndexed, MUST_IMPLEMENT_MSG);
        Assert.isTrue(!(isNamed && isIndexed), ONLY_ONE_MSG);

        return operationClass.getConstructor(Method.class, String.class, OperationContext.class).newInstance(method,
                sql, operationContext);
    }

    @Override
    public Object instantiate() {
        // FIXME: needs to handle abstract prototype classes too
        return Proxy.newProxyInstance(prototype.getClassLoader(), new Class[] { prototype }, invocationHandler);
    }
}