/** * Copyright (c) 2010 Yahoo! 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. See accompanying LICENSE file. */ package org.apache.oozie.service; import java.io.IOException; import java.text.MessageFormat; import java.util.Properties; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.apache.hadoop.conf.Configuration; import org.apache.oozie.BundleActionBean; import org.apache.oozie.BundleJobBean; import org.apache.oozie.CoordinatorActionBean; import org.apache.oozie.CoordinatorJobBean; import org.apache.oozie.ErrorCode; import org.apache.oozie.FaultInjection; import org.apache.oozie.SLAEventBean; import org.apache.oozie.WorkflowActionBean; import org.apache.oozie.WorkflowJobBean; import org.apache.oozie.client.rest.JsonBundleJob; import org.apache.oozie.client.rest.JsonCoordinatorAction; import org.apache.oozie.client.rest.JsonCoordinatorJob; import org.apache.oozie.client.rest.JsonSLAEvent; import org.apache.oozie.client.rest.JsonWorkflowAction; import org.apache.oozie.client.rest.JsonWorkflowJob; import org.apache.oozie.executor.jpa.JPAExecutor; import org.apache.oozie.executor.jpa.JPAExecutorException; import org.apache.oozie.util.IOUtils; import org.apache.oozie.util.Instrumentable; import org.apache.oozie.util.Instrumentation; import org.apache.oozie.util.XLog; import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; /** * Service that manages JPA and executes {@link JPAExecutor}. */ public class JPAService implements Service, Instrumentable { private static final String INSTRUMENTATION_GROUP = "jpa"; public static final String CONF_DB_SCHEMA = "oozie.db.schema.name"; public static final String CONF_PREFIX = Service.CONF_PREFIX + "JPAService."; public static final String CONF_URL = CONF_PREFIX + "jdbc.url"; public static final String CONF_DRIVER = CONF_PREFIX + "jdbc.driver"; public static final String CONF_USERNAME = CONF_PREFIX + "jdbc.username"; public static final String CONF_PASSWORD = CONF_PREFIX + "jdbc.password"; public static final String CONF_MAX_ACTIVE_CONN = CONF_PREFIX + "pool.max.active.conn"; public static final String CONF_CREATE_DB_SCHEMA = CONF_PREFIX + "create.db.schema"; public static final String CONF_VALIDATE_DB_CONN = CONF_PREFIX + "validate.db.connection"; private EntityManagerFactory factory; private Instrumentation instr; private static XLog LOG; /** * Return the public interface of the service. * * @return {@link JPAService}. */ public Class<? extends Service> getInterface() { return JPAService.class; } @Override public void instrument(Instrumentation instr) { this.instr = instr; } /** * Initializes the {@link JPAService}. * * @param services services instance. */ public void init(Services services) throws ServiceException { LOG = XLog.getLog(JPAService.class); Configuration conf = services.getConf(); String dbSchema = conf.get(CONF_DB_SCHEMA, "oozie"); String url = conf.get(CONF_URL, "jdbc:derby:${oozie.home.dir}/${oozie.db.schema.name}-db;create=true"); String driver = conf.get(CONF_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver"); String user = conf.get(CONF_USERNAME, "sa"); String password = conf.get(CONF_PASSWORD, "").trim(); String maxConn = conf.get(CONF_MAX_ACTIVE_CONN, "10").trim(); boolean autoSchemaCreation = conf.getBoolean(CONF_CREATE_DB_SCHEMA, true); boolean validateDbConn = conf.getBoolean(CONF_VALIDATE_DB_CONN, false); if (!url.startsWith("jdbc:")) { throw new ServiceException(ErrorCode.E0608, url, "invalid JDBC URL, must start with 'jdbc:'"); } String dbType = url.substring("jdbc:".length()); if (dbType.indexOf(":") <= 0) { throw new ServiceException(ErrorCode.E0608, url, "invalid JDBC URL, missing vendor 'jdbc:[VENDOR]:...'"); } dbType = dbType.substring(0, dbType.indexOf(":")); String persistentUnit = "oozie-" + dbType; // Checking existince of ORM file for DB type String ormFile = "META-INF/" + persistentUnit + "-orm.xml"; try { IOUtils.getResourceAsStream(ormFile, -1); } catch (IOException ex) { throw new ServiceException(ErrorCode.E0609, dbType, ormFile); } String connProps = "DriverClassName={0},Url={1},Username={2},Password={3},MaxActive={4}"; connProps = MessageFormat.format(connProps, driver, url, user, password, maxConn); Properties props = new Properties(); if (autoSchemaCreation) { props.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); } else if (validateDbConn) { // validation can be done only if the schema already exist, else a // connection cannot be obtained to create the schema. connProps += ",TestOnBorrow=true,TestOnReturn=false,TestWhileIdle=false"; connProps += ",ValidationQuery=select count(*) from VALIDATE_CONN"; connProps = MessageFormat.format(connProps, dbSchema); } props.setProperty("openjpa.ConnectionProperties", connProps); factory = Persistence.createEntityManagerFactory(persistentUnit, props); EntityManager entityManager = getEntityManager(); entityManager.find(WorkflowActionBean.class, 1); entityManager.find(WorkflowJobBean.class, 1); entityManager.find(CoordinatorActionBean.class, 1); entityManager.find(CoordinatorJobBean.class, 1); entityManager.find(JsonWorkflowAction.class, 1); entityManager.find(JsonWorkflowJob.class, 1); entityManager.find(JsonCoordinatorAction.class, 1); entityManager.find(JsonCoordinatorJob.class, 1); entityManager.find(SLAEventBean.class, 1); entityManager.find(JsonSLAEvent.class, 1); entityManager.find(BundleJobBean.class, 1); entityManager.find(JsonBundleJob.class, 1); entityManager.find(BundleActionBean.class, 1); LOG.info(XLog.STD, "All entities initialized"); // need to use a pseudo no-op transaction so all entities, datasource // and connection pool are initialized one time only entityManager.getTransaction().begin(); OpenJPAEntityManagerFactorySPI spi = (OpenJPAEntityManagerFactorySPI) factory; LOG.info("JPA configuration: {0}", spi.getConfiguration().getConnectionProperties()); entityManager.getTransaction().commit(); entityManager.close(); } /** * Destroy the JPAService */ public void destroy() { if (factory != null && factory.isOpen()) { factory.close(); } } /** * Execute a {@link JPAExecutor}. * * @param executor JPAExecutor to execute. * @return return value of the JPAExecutor. * @throws JPAExecutorException thrown if an jpa executor failed */ public <T> T execute(JPAExecutor<T> executor) throws JPAExecutorException { EntityManager em = getEntityManager(); Instrumentation.Cron cron = new Instrumentation.Cron(); try { LOG.trace("Executing JPAExecutor [{0}]", executor.getName()); if (instr != null) { instr.incr(INSTRUMENTATION_GROUP, executor.getName(), 1); } cron.start(); em.getTransaction().begin(); T t = executor.execute(em); if (em.getTransaction().isActive()) { if (FaultInjection.isActive("org.apache.oozie.command.SkipCommitFaultInjection")) { throw new RuntimeException("Skipping Commit for Failover Testing"); } em.getTransaction().commit(); } return t; } finally { cron.stop(); if (instr != null) { instr.addCron(INSTRUMENTATION_GROUP, executor.getName(), cron); } try { if (em.getTransaction().isActive()) { LOG.warn("JPAExecutor [{0}] ended with an active transaction, rolling back", executor.getName()); em.getTransaction().rollback(); } } catch (Exception ex) { LOG.warn("Could not check/rollback transaction after JPAExecutor [{0}], {1}", executor.getName(), ex .getMessage(), ex); } try { if (em.isOpen()) { em.close(); } else { LOG.warn("JPAExecutor [{0}] closed the EntityManager, it should not!", executor.getName()); } } catch (Exception ex) { LOG.warn("Could not close EntityManager after JPAExecutor [{0}], {1}", executor.getName(), ex .getMessage(), ex); } } } /** * Return an EntityManager. Used by the StoreService. Once the StoreService is removed this method must be removed. * * @return an entity manager */ EntityManager getEntityManager() { return factory.createEntityManager(); } }