package net.ttddyy.dsproxy.support.jndi;

import net.ttddyy.dsproxy.listener.logging.CommonsLogLevel;
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import net.ttddyy.dsproxy.transform.ParameterTransformer;
import net.ttddyy.dsproxy.transform.QueryTransformer;

import javax.naming.*;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

/**
 * JNDI ObjectFactory to create {@link net.ttddyy.dsproxy.support.ProxyDataSource}.
 *
 * <pre>{@code
 * <Resource name="jdbc/global/myProxy"
 *           auth="Container"
 *           type="net.ttddyy.dsproxy.support.ProxyDataSource"
 *           factory="net.ttddyy.dsproxy.support.jndi.ProxyDataSourceObjectFactory"
 *           description="ds"
 *           listeners="count,sysout,org.example.SampleListener"
 *           proxyName="DS-PROXY"
 *           format="json"
 *           dataSource="[REFERENCE_TO_ACTUAL_DATASOURCE_RESOURCE]"  <!-- ex: java:jdbc/global/myDS -->
 * />
 * }</pre>
 *
 * Parameters:
 * <ul>
 * <li> <b>dataSource <i>(required)</i></b>: Reference to actual datasource resource.  ex: java:jdbc/global/myDS
 * <li> <b>proxyName</b>:             ProxyDataSource name
 * <li> <b>logLevel</b>:              Loglevel for commons-logging or slf4j. ex: DEBUG, INFO, etc.
 * <li> <b>listeners</b>:             Fully qualified class name of `QueryExecutionListener` implementation class,or predefined values below. Can be comma delimited.
 * <li> <b>queryTransformer</b>:      Fully qualified class name of `QueryTransformer` implementation class.
 * <li> <b>parameterTransformer</b>:  Fully qualified class name of `ParameterTransformer` implementation class.
 * </ul>
 *
 * <i>listeners</i> parameter:
 * <ul>
 * <li> <b>sysout</b>:   alias to `net.ttddyy.dsproxy.listener.logging.SystemOutQueryLoggingListener`
 * <li> <b>commons</b>:  alias to `net.ttddyy.dsproxy.listener.logging.CommonsQueryLoggingListener`
 * <li> <b>slf4j</b>:    alias to `net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener`
 * <li> <b>count</b>:    alias to `net.ttddyy.dsproxy.listener.DataSourceQueryCountListener`
 * <li> <b>x.y.z.MyQueryExecutionListener</b>: Fully qualified class name of `QueryExecutionListener` implementation
 * </ul>
 *
 * <i>format</i> parameter:
 * <ul>
 * <li> <b>json</b>: set logging output format as JSON
 * </ul>
 *
 * @author Tadaya Tsuyukubo
 * @since 1.3
 */
public class ProxyDataSourceObjectFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {

        if (obj == null || !(obj instanceof Reference)) {
            return null;
        }
        Reference reference = (Reference) obj;

        String dataSourceJndiName = getContentFromReference(reference, "dataSource");
        String proxyDataSourceName = getContentFromReference(reference, "proxyName");
        String listenerNames = getContentFromReference(reference, "listeners");
        String logLevel = getContentFromReference(reference, "logLevel");
        String loggerName = getContentFromReference(reference, "loggerName");
        String foramt = getContentFromReference(reference, "format");
        String queryTransformer = getContentFromReference(reference, "queryTransformer");
        String parameterTransformer = getContentFromReference(reference, "parameterTransformer");

        // retrieve datasource from JNDI
        Object dataSourceResource = new InitialContext().lookup(dataSourceJndiName);
        if (dataSourceResource == null) {
            throw new Exception(String.format("%s is not available.", dataSourceJndiName));
        } else if (!(dataSourceResource instanceof DataSource)) {
            throw new Exception(String.format("%s is not DataSource: %s", dataSourceJndiName, dataSourceResource));
        }
        DataSource dataSource = (DataSource) dataSourceResource;

        // builder
        ProxyDataSourceBuilder builder = ProxyDataSourceBuilder.create(dataSource);

        if (proxyDataSourceName != null) {
            builder.name(proxyDataSourceName);
        }

        if (listenerNames != null) {
            for (String listenerName : getListenerNames(listenerNames)) {
                if ("commons".equalsIgnoreCase(listenerName)) {
                    boolean hasLogLevel = logLevel != null;
                    boolean hasLoggerName = loggerName != null;
                    if (hasLogLevel && hasLoggerName) {
                        builder.logQueryByCommons(CommonsLogLevel.valueOf(logLevel.toUpperCase()), loggerName);
                    } else if (hasLogLevel) {
                        builder.logQueryByCommons(CommonsLogLevel.valueOf(logLevel.toUpperCase()));
                    } else if (hasLoggerName) {
                        builder.logQueryByCommons(loggerName);
                    } else {
                        builder.logQueryByCommons();
                    }
                } else if ("slf4j".equalsIgnoreCase(listenerName)) {
                    boolean hasLogLevel = logLevel != null;
                    boolean hasLogName = loggerName != null;
                    if (hasLogLevel && hasLogName) {
                        builder.logQueryBySlf4j(SLF4JLogLevel.valueOf(logLevel.toUpperCase()), loggerName);
                    } else if (hasLogLevel) {
                        builder.logQueryBySlf4j(SLF4JLogLevel.valueOf(logLevel.toUpperCase()));
                    } else if (hasLogName) {
                        builder.logQueryBySlf4j(loggerName);
                    } else {
                        builder.logQueryBySlf4j();
                    }
                } else if ("sysout".equalsIgnoreCase(listenerName)) {
                    builder.logQueryToSysOut();
                } else if ("count".equalsIgnoreCase(listenerName)) {
                    builder.countQuery();
                } else {
                    QueryExecutionListener listener = createNewInstance(QueryExecutionListener.class, listenerName);
                    builder.listener(listener);
                }
            }

            if (foramt != null && "json".equals(foramt.toLowerCase())) {
                builder.asJson();
            }
        }

        if (queryTransformer != null) {
            QueryTransformer transformer = createNewInstance(QueryTransformer.class, queryTransformer);
            builder.queryTransformer(transformer);
        }
        if (parameterTransformer != null) {
            ParameterTransformer transformer = createNewInstance(ParameterTransformer.class, parameterTransformer);
            builder.parameterTransformer(transformer);
        }

        return builder.build();
    }

    @SuppressWarnings("unchecked")
    protected <T> T createNewInstance(Class<T> clazz, String className) throws Exception {
        try {
            return (T) Class.forName(className).newInstance();
        } catch (Exception e) {
            String msg = String.format("Failed to create %s - %s: %s", clazz.getSimpleName(), e.getClass().getName(), e.getMessage());
            throw new Exception(msg);
        }
    }

    protected String getContentFromReference(Reference reference, String key) {
        RefAddr refAddr = reference.get(key);
        if (refAddr == null) {
            return null;
        }
        return refAddr.getContent().toString();
    }

    protected String[] getListenerNames(String listenerNames) {
        Set<String> listenerNameSet = new HashSet<String>();
        for (String listenerName : listenerNames.split(",")) {
            listenerNameSet.add(trim(listenerName));
        }
        return listenerNameSet.toArray(new String[listenerNameSet.size()]);

    }

    protected String trim(String s) {
        int length = s.length();
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (!Character.isWhitespace(c)) {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

@@ -3330,18 +3330,18 @@
 ring for
-a
 m
+a
 t = getC
@@ -6294,18 +6294,18 @@
  if (for
-a
 m
+a
 t != nul
@@ -6326,18 +6326,18 @@
 uals(for
-a
 m
+a
 t.toLowe

Fix typo