com.wavemaker.runtime.data.spring.SpringDataServiceManager.java Source code

Java tutorial

Introduction

Here is the source code for com.wavemaker.runtime.data.spring.SpringDataServiceManager.java

Source

/*
 *  Copyright (C) 2012-2013 CloudJee, Inc. All rights reserved.
 *
 *  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.wavemaker.runtime.data.spring;

import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import com.wavemaker.common.WMRuntimeException;
import org.apache.commons.logging.Log;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import com.wavemaker.common.util.StringUtils;
import com.wavemaker.runtime.WMAppContext;
import com.wavemaker.runtime.data.DataServiceLoggers;
import com.wavemaker.runtime.data.DataServiceManager;
import com.wavemaker.runtime.data.DataServiceMetaData;
import com.wavemaker.runtime.data.DataServiceRuntimeException;
import com.wavemaker.runtime.data.DefaultTaskManager;
import com.wavemaker.runtime.data.Task;
import com.wavemaker.runtime.data.TaskManager;
import com.wavemaker.runtime.data.ThreadContext;
import com.wavemaker.runtime.data.hibernate.DataServiceMetaData_Hib;
import com.wavemaker.runtime.data.task.DefaultRollback;
import com.wavemaker.runtime.data.task.PreProcessor;
import com.wavemaker.runtime.data.util.DataServiceConstants;
import com.wavemaker.runtime.data.util.QueryHandler;

/**
 * @author Simon Toens
 */
public class SpringDataServiceManager implements DataServiceManager {

    public static final Log txLogger = DataServiceLoggers.transactionLogger;

    private final PlatformTransactionManager txMgr;

    private final HibernateTemplate hibernateTemplate;

    private final TaskManager taskMgr;

    private final DataServiceMetaData metaData;

    private final Configuration cfg;

    private final SessionFactory sessionFactory;

    public SpringDataServiceManager(String configurationName, HibernateTemplate hibernateTemplate,
            PlatformTransactionManager txMgr) {

        this(configurationName, hibernateTemplate, txMgr, DefaultTaskManager.getInstance(),
                Collections.<String, String>emptyMap());
    }

    public SpringDataServiceManager(String configurationName, HibernateTemplate hibernateTemplate,
            PlatformTransactionManager txMgr, TaskManager taskMgr, Map<String, String> properties) {
        if (configurationName == null) {
            throw new IllegalArgumentException("configurationName must be set");
        }

        if (hibernateTemplate == null) {
            throw new IllegalArgumentException("hibernateTemplate must be set");
        }

        if (txMgr == null) {
            throw new IllegalArgumentException("platformTransactionManager must be set");
        }

        if (taskMgr == null) {
            throw new IllegalArgumentException("taskManager must be set");
        }

        this.hibernateTemplate = hibernateTemplate;
        this.txMgr = txMgr;
        this.taskMgr = taskMgr;

        ConfigurationRegistry reg = ConfigurationRegistry.getInstance();
        this.cfg = reg.getConfiguration(configurationName);
        if (this.cfg == null) {
            throw new DataServiceRuntimeException("Cannot find configuration for " + configurationName);
        }

        this.sessionFactory = reg.getSessionFactory(configurationName);
        if (this.sessionFactory == null) {
            throw new DataServiceRuntimeException("Cannot find session factory for " + configurationName);
        }

        String s = properties.get(DataServiceConstants.GENERATE_OLD_STYLE_OPRS_PROPERTY);

        boolean useIndividualCRUDOperations = Boolean.valueOf(s);

        this.metaData = initMetaData(configurationName, this.cfg, hibernateTemplate, useIndividualCRUDOperations,
                properties);
    }

    @Override
    public DataServiceMetaData getMetaData() {
        return this.metaData;
    }

    @Override
    public Session getSession() {
        if (!isTxRunning()) {
            txLogger.warn("begin a tx before accessing the session");
        }
        return (Session) invoke(this.taskMgr.getSessionTask());
    }

    @Override
    public void begin() {
        if (txLogger.isInfoEnabled()) {
            txLogger.info("begin");
        }

        ThreadContext.Context ctx = ThreadContext.getContext(this.metaData.getName());
        if (ctx == null) {
            ctx = new ThreadContext.Context(this.txMgr, this.metaData, this.cfg, this.sessionFactory);
        }

        TransactionStatus txStatus = ctx.getTransactionStatus();

        if (txStatus == null) {
            txStatus = this.txMgr.getTransaction(null);
            ctx.setTransactionStatus(txStatus);
        } else {
            if (txLogger.isWarnEnabled()) {
                txLogger.warn("ignoring begin - tx already in progress");
                if (txLogger.isDebugEnabled()) {
                    logStackTrace();
                }
            }
        }
    }

    @Override
    public void commit() {

        if (txLogger.isInfoEnabled()) {
            txLogger.info("commit");
        }

        ThreadContext.Context ctx = ThreadContext.getContext(this.metaData.getName());

        if (ctx == null) {
            if (txLogger.isWarnEnabled()) {
                txLogger.warn("ignoring commit - no tx in progress");
                if (txLogger.isDebugEnabled()) {
                    logStackTrace();
                }
            }
            return;
        }

        TransactionStatus txStatus = ctx.getTransactionStatus();

        try {
            if (txStatus == null) {
                if (txLogger.isWarnEnabled()) {
                    txLogger.warn("ignoring commit - no tx status");
                    if (txLogger.isDebugEnabled()) {
                        logStackTrace();
                    }
                }
            } else {
                this.txMgr.commit(txStatus);
            }
        } finally {
            ctx.setTransactionStatus(null);
            ThreadContext.unsetContext(this.metaData.getName());
            HashMap<String, ThreadContext.Context> contextHash = ThreadContext.getThreadLocalHash();

            if (contextHash != null && contextHash.size() > 0) {
                if (!TransactionSynchronizationManager.isSynchronizationActive()) {
                    TransactionSynchronizationManager.initSynchronization();
                }
            } else {
                if (TransactionSynchronizationManager.isSynchronizationActive()) {
                    TransactionSynchronizationManager.clear();
                    Map map = TransactionSynchronizationManager.getResourceMap();
                    for (Object entry : map.keySet()) {
                        TransactionSynchronizationManager.unbindResource(entry);
                    }
                }
            }
        }
    }

    @Override
    public void rollback() {

        if (txLogger.isInfoEnabled()) {
            txLogger.info("rollback");
        }

        ThreadContext.Context ctx = ThreadContext.getContext(this.metaData.getName());

        if (ctx == null) {
            if (txLogger.isWarnEnabled()) {
                txLogger.warn("ignoring rollback - no tx in progress");
                if (txLogger.isDebugEnabled()) {
                    logStackTrace();
                }
            }
            return;
        }

        TransactionStatus txStatus = ctx.getTransactionStatus();

        try {

            if (txStatus == null) {
                if (txLogger.isWarnEnabled()) {
                    txLogger.warn("ignoring rollback - no tx status");
                    if (txLogger.isDebugEnabled()) {
                        logStackTrace();
                    }
                }
            } else {
                this.txMgr.rollback(txStatus);
            }
        } finally {
            ctx.setTransactionStatus(null);
            ThreadContext.unsetContext(this.metaData.getName());
        }
    }

    @Override
    public Object invoke(Task task, Object... input) {
        boolean unset = false;
        ThreadContext.Context ctx = ThreadContext.getContext(this.metaData.getName());
        if (ctx == null) {
            ctx = new ThreadContext.Context(this.txMgr, this.metaData, this.cfg, this.sessionFactory);
            unset = true;
        }
        try {
            return runInTx(task, input);
        } finally {
            if (unset) {
                ThreadContext.unsetContext(this.metaData.getName());
            }
        }
    }

    @Override
    public void dispose() {
        try {
            if (isTxRunning()) {
                rollback();
            }
            // access sessionFactory here and close it too
            getMetaData().dispose();
        } catch (RuntimeException ignore) {
        }
    }

    private boolean isTxRunning() {
        ThreadContext.Context ctx = ThreadContext.getContext(this.metaData.getName());
        if (ctx == null) {
            return false;
        }
        return ctx.getTransactionStatus() != null;
    }

    private Object runInTx(Task task, Object... input) {
        HibernateCallback action = new RunInHibernate(task, input);
        TransactionTemplate txTemplate = new TransactionTemplate(this.txMgr);
        boolean rollbackOnly = task instanceof DefaultRollback && !isTxRunning();
        RunInTx tx = new RunInTx(action, rollbackOnly);
        if (txLogger.isInfoEnabled()) {
            if (isTxRunning()) {
                txLogger.info("tx is running executing \"" + task.getName() + "\" in current tx");
            } else {
                txLogger.info("no tx running, wrapping execution of \"" + task.getName() + "\" in tx");
                if (rollbackOnly) {
                    txLogger.info("rollback enabled for \"" + task.getName() + "\"");
                }
            }
        }
        Object rtn = null;
        try {
            rtn = txTemplate.execute(tx);
        } catch (Throwable ex) {
            //The following logic intends to display a sensible message for the user when a column contains a value whose length
            //exceeds the maximum length allowed in the database.  The logic has been tested on MySQL, Postgres, Oracle and
            //SQLServer so far.
            if (ex.getCause() instanceof java.sql.BatchUpdateException) { //Oracle
                String msg = ((java.sql.BatchUpdateException) ex.getCause()).getNextException().getMessage();
                if (msg != null) {
                    ex.printStackTrace();
                    throw new WMRuntimeException(msg);
                }
            } else if (ex.getCause().getCause() instanceof java.sql.BatchUpdateException) { //Postgres
                java.sql.BatchUpdateException e = (java.sql.BatchUpdateException) ex.getCause().getCause();
                if (e != null && e.getMessage() != null) {
                    ex.printStackTrace();
                    throw new WMRuntimeException(e.getNextException().getMessage());
                }
            } else if (ex.getCause().getCause() != null) { //MySQL, SQLServer
                String msg = ex.getCause().getCause().getMessage();
                if (msg != null) {
                    ex.printStackTrace();
                    throw new WMRuntimeException(msg);
                }
            } else {
                throw new WMRuntimeException(ex);
            }
        }
        if (txLogger.isInfoEnabled()) {
            if (isTxRunning()) {
                txLogger.info("tx is running after execution of \"" + task.getName() + "\"");
            } else {
                txLogger.info("tx is not running after execution of \"" + task.getName() + "\"");
            }

        }
        return rtn;
    }

    private void logStackTrace() {
        txLogger.debug(StringUtils.toString(new Throwable()));
    }

    private class RunInHibernate implements HibernateCallback {

        private final Task task;

        private Object[] input;

        RunInHibernate(Task task, Object[] input) {
            this.task = task;
            this.input = input;
        }

        @Override
        public Object doInHibernate(Session session) {

            Task preProcessorTask = ThreadContext.getPreProcessorTask();

            if (preProcessorTask != null && this.task instanceof PreProcessor) {
                if (txLogger.isInfoEnabled()) {
                    txLogger.info("Running preprocessor task " + preProcessorTask.getName());
                }
                this.input = (Object[]) preProcessorTask.run(session,
                        SpringDataServiceManager.this.metaData.getName(), this.input, this.task,
                        SpringDataServiceManager.this.taskMgr);
            }

            WMAppContext wmApp = WMAppContext.getInstance();
            if (wmApp != null && wmApp.isMultiTenant()) {
                Class[] clsArr = new Class[] { Session.class };
                ClassLoader cl = Session.class.getClassLoader();
                QueryHandler qh = new QueryHandler(session,
                        SpringDataServiceManager.this.metaData.getConfiguration());
                session = (Session) Proxy.newProxyInstance(cl, clsArr, qh);
            }

            return this.task.run(session, SpringDataServiceManager.this.metaData.getName(), this.input);
        }

    }

    private class RunInTx implements TransactionCallback {

        private final HibernateCallback action;

        private final boolean rollbackOnly;

        RunInTx(HibernateCallback action, boolean rollbackOnly) {
            this.action = action;
            this.rollbackOnly = rollbackOnly;
        }

        @Override
        public Object doInTransaction(TransactionStatus status) {
            if (this.rollbackOnly) {
                status.setRollbackOnly();
            }
            return SpringDataServiceManager.this.hibernateTemplate.execute(this.action);
        }
    }

    private static DataServiceMetaData initMetaData(String configurationName, Configuration cfg,
            HibernateTemplate htemp, final boolean useIndividualCRUDOperations,
            final Map<String, String> properties) {
        final DataServiceMetaData rtn = new DataServiceMetaData_Hib(configurationName, cfg, properties); // salesforce

        htemp.execute(new HibernateCallback() {

            @Override
            public Object doInHibernate(Session session) {
                rtn.init(session, useIndividualCRUDOperations);
                return null;
            }
        });

        return rtn;
    }

    @Override
    public Object invoke(Task task, Map<String, Class<?>> types, boolean named, Object... input) { // salesforce
        return null;
    }

}