001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.service; 016 017 import java.io.IOException; 018 import java.text.MessageFormat; 019 import java.util.Properties; 020 021 import javax.persistence.EntityManager; 022 import javax.persistence.EntityManagerFactory; 023 import javax.persistence.Persistence; 024 025 import org.apache.hadoop.conf.Configuration; 026 import org.apache.oozie.BundleActionBean; 027 import org.apache.oozie.BundleJobBean; 028 import org.apache.oozie.CoordinatorActionBean; 029 import org.apache.oozie.CoordinatorJobBean; 030 import org.apache.oozie.ErrorCode; 031 import org.apache.oozie.FaultInjection; 032 import org.apache.oozie.SLAEventBean; 033 import org.apache.oozie.WorkflowActionBean; 034 import org.apache.oozie.WorkflowJobBean; 035 import org.apache.oozie.client.rest.JsonBundleJob; 036 import org.apache.oozie.client.rest.JsonCoordinatorAction; 037 import org.apache.oozie.client.rest.JsonCoordinatorJob; 038 import org.apache.oozie.client.rest.JsonSLAEvent; 039 import org.apache.oozie.client.rest.JsonWorkflowAction; 040 import org.apache.oozie.client.rest.JsonWorkflowJob; 041 import org.apache.oozie.executor.jpa.JPAExecutor; 042 import org.apache.oozie.executor.jpa.JPAExecutorException; 043 import org.apache.oozie.util.IOUtils; 044 import org.apache.oozie.util.Instrumentable; 045 import org.apache.oozie.util.Instrumentation; 046 import org.apache.oozie.util.XLog; 047 import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; 048 049 /** 050 * Service that manages JPA and executes {@link JPAExecutor}. 051 */ 052 public class JPAService implements Service, Instrumentable { 053 private static final String INSTRUMENTATION_GROUP = "jpa"; 054 055 public static final String CONF_DB_SCHEMA = "oozie.db.schema.name"; 056 057 public static final String CONF_PREFIX = Service.CONF_PREFIX + "JPAService."; 058 public static final String CONF_URL = CONF_PREFIX + "jdbc.url"; 059 public static final String CONF_DRIVER = CONF_PREFIX + "jdbc.driver"; 060 public static final String CONF_USERNAME = CONF_PREFIX + "jdbc.username"; 061 public static final String CONF_PASSWORD = CONF_PREFIX + "jdbc.password"; 062 public static final String CONF_MAX_ACTIVE_CONN = CONF_PREFIX + "pool.max.active.conn"; 063 public static final String CONF_CREATE_DB_SCHEMA = CONF_PREFIX + "create.db.schema"; 064 public static final String CONF_VALIDATE_DB_CONN = CONF_PREFIX + "validate.db.connection"; 065 066 private EntityManagerFactory factory; 067 private Instrumentation instr; 068 069 private static XLog LOG; 070 071 /** 072 * Return the public interface of the service. 073 * 074 * @return {@link JPAService}. 075 */ 076 public Class<? extends Service> getInterface() { 077 return JPAService.class; 078 } 079 080 @Override 081 public void instrument(Instrumentation instr) { 082 this.instr = instr; 083 } 084 085 /** 086 * Initializes the {@link JPAService}. 087 * 088 * @param services services instance. 089 */ 090 public void init(Services services) throws ServiceException { 091 LOG = XLog.getLog(JPAService.class); 092 Configuration conf = services.getConf(); 093 String dbSchema = conf.get(CONF_DB_SCHEMA, "oozie"); 094 String url = conf.get(CONF_URL, "jdbc:derby:${oozie.home.dir}/${oozie.db.schema.name}-db;create=true"); 095 String driver = conf.get(CONF_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver"); 096 String user = conf.get(CONF_USERNAME, "sa"); 097 String password = conf.get(CONF_PASSWORD, "").trim(); 098 String maxConn = conf.get(CONF_MAX_ACTIVE_CONN, "10").trim(); 099 boolean autoSchemaCreation = conf.getBoolean(CONF_CREATE_DB_SCHEMA, true); 100 boolean validateDbConn = conf.getBoolean(CONF_VALIDATE_DB_CONN, false); 101 102 if (!url.startsWith("jdbc:")) { 103 throw new ServiceException(ErrorCode.E0608, url, "invalid JDBC URL, must start with 'jdbc:'"); 104 } 105 String dbType = url.substring("jdbc:".length()); 106 if (dbType.indexOf(":") <= 0) { 107 throw new ServiceException(ErrorCode.E0608, url, "invalid JDBC URL, missing vendor 'jdbc:[VENDOR]:...'"); 108 } 109 dbType = dbType.substring(0, dbType.indexOf(":")); 110 111 String persistentUnit = "oozie-" + dbType; 112 113 // Checking existince of ORM file for DB type 114 String ormFile = "META-INF/" + persistentUnit + "-orm.xml"; 115 try { 116 IOUtils.getResourceAsStream(ormFile, -1); 117 } 118 catch (IOException ex) { 119 throw new ServiceException(ErrorCode.E0609, dbType, ormFile); 120 } 121 122 String connProps = "DriverClassName={0},Url={1},Username={2},Password={3},MaxActive={4}"; 123 connProps = MessageFormat.format(connProps, driver, url, user, password, maxConn); 124 Properties props = new Properties(); 125 if (autoSchemaCreation) { 126 props.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); 127 } 128 else if (validateDbConn) { 129 // validation can be done only if the schema already exist, else a 130 // connection cannot be obtained to create the schema. 131 connProps += ",TestOnBorrow=true,TestOnReturn=false,TestWhileIdle=false"; 132 connProps += ",ValidationQuery=select count(*) from VALIDATE_CONN"; 133 connProps = MessageFormat.format(connProps, dbSchema); 134 } 135 props.setProperty("openjpa.ConnectionProperties", connProps); 136 137 factory = Persistence.createEntityManagerFactory(persistentUnit, props); 138 139 EntityManager entityManager = getEntityManager(); 140 entityManager.find(WorkflowActionBean.class, 1); 141 entityManager.find(WorkflowJobBean.class, 1); 142 entityManager.find(CoordinatorActionBean.class, 1); 143 entityManager.find(CoordinatorJobBean.class, 1); 144 entityManager.find(JsonWorkflowAction.class, 1); 145 entityManager.find(JsonWorkflowJob.class, 1); 146 entityManager.find(JsonCoordinatorAction.class, 1); 147 entityManager.find(JsonCoordinatorJob.class, 1); 148 entityManager.find(SLAEventBean.class, 1); 149 entityManager.find(JsonSLAEvent.class, 1); 150 entityManager.find(BundleJobBean.class, 1); 151 entityManager.find(JsonBundleJob.class, 1); 152 entityManager.find(BundleActionBean.class, 1); 153 154 LOG.info(XLog.STD, "All entities initialized"); 155 // need to use a pseudo no-op transaction so all entities, datasource 156 // and connection pool are initialized one time only 157 entityManager.getTransaction().begin(); 158 OpenJPAEntityManagerFactorySPI spi = (OpenJPAEntityManagerFactorySPI) factory; 159 LOG.info("JPA configuration: {0}", spi.getConfiguration().getConnectionProperties()); 160 entityManager.getTransaction().commit(); 161 entityManager.close(); 162 } 163 164 /** 165 * Destroy the JPAService 166 */ 167 public void destroy() { 168 if (factory != null && factory.isOpen()) { 169 factory.close(); 170 } 171 } 172 173 /** 174 * Execute a {@link JPAExecutor}. 175 * 176 * @param executor JPAExecutor to execute. 177 * @return return value of the JPAExecutor. 178 * @throws JPAExecutorException thrown if an jpa executor failed 179 */ 180 public <T> T execute(JPAExecutor<T> executor) throws JPAExecutorException { 181 EntityManager em = getEntityManager(); 182 Instrumentation.Cron cron = new Instrumentation.Cron(); 183 try { 184 LOG.trace("Executing JPAExecutor [{0}]", executor.getName()); 185 if (instr != null) { 186 instr.incr(INSTRUMENTATION_GROUP, executor.getName(), 1); 187 } 188 cron.start(); 189 em.getTransaction().begin(); 190 T t = executor.execute(em); 191 if (em.getTransaction().isActive()) { 192 if (FaultInjection.isActive("org.apache.oozie.command.SkipCommitFaultInjection")) { 193 throw new RuntimeException("Skipping Commit for Failover Testing"); 194 } 195 196 em.getTransaction().commit(); 197 } 198 return t; 199 } 200 finally { 201 cron.stop(); 202 if (instr != null) { 203 instr.addCron(INSTRUMENTATION_GROUP, executor.getName(), cron); 204 } 205 try { 206 if (em.getTransaction().isActive()) { 207 LOG.warn("JPAExecutor [{0}] ended with an active transaction, rolling back", executor.getName()); 208 em.getTransaction().rollback(); 209 } 210 } 211 catch (Exception ex) { 212 LOG.warn("Could not check/rollback transaction after JPAExecutor [{0}], {1}", executor.getName(), ex 213 .getMessage(), ex); 214 } 215 try { 216 if (em.isOpen()) { 217 em.close(); 218 } 219 else { 220 LOG.warn("JPAExecutor [{0}] closed the EntityManager, it should not!", executor.getName()); 221 } 222 } 223 catch (Exception ex) { 224 LOG.warn("Could not close EntityManager after JPAExecutor [{0}], {1}", executor.getName(), ex 225 .getMessage(), ex); 226 } 227 } 228 } 229 230 /** 231 * Return an EntityManager. Used by the StoreService. Once the StoreService is removed this method must be removed. 232 * 233 * @return an entity manager 234 */ 235 EntityManager getEntityManager() { 236 return factory.createEntityManager(); 237 } 238 239 }