001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender.db.jpa; 018 019import java.lang.reflect.Constructor; 020 021import javax.persistence.EntityManager; 022import javax.persistence.EntityManagerFactory; 023import javax.persistence.EntityTransaction; 024import javax.persistence.Persistence; 025 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.appender.AppenderLoggingException; 028import org.apache.logging.log4j.core.appender.ManagerFactory; 029import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; 030 031/** 032 * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JPA. 033 */ 034public final class JpaDatabaseManager extends AbstractDatabaseManager { 035 private static final JPADatabaseManagerFactory FACTORY = new JPADatabaseManagerFactory(); 036 037 private final String entityClassName; 038 private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; 039 private final String persistenceUnitName; 040 041 private EntityManagerFactory entityManagerFactory; 042 043 private EntityManager entityManager; 044 private EntityTransaction transaction; 045 046 private JpaDatabaseManager(final String name, final int bufferSize, 047 final Class<? extends AbstractLogEventWrapperEntity> entityClass, 048 final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, 049 final String persistenceUnitName) { 050 super(name, bufferSize); 051 this.entityClassName = entityClass.getName(); 052 this.entityConstructor = entityConstructor; 053 this.persistenceUnitName = persistenceUnitName; 054 } 055 056 @Override 057 protected void startupInternal() { 058 this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName); 059 } 060 061 @Override 062 protected void shutdownInternal() { 063 if (this.entityManager != null || this.transaction != null) { 064 this.commitAndClose(); 065 } 066 if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { 067 this.entityManagerFactory.close(); 068 } 069 } 070 071 @Override 072 protected void connectAndStart() { 073 try { 074 this.entityManager = this.entityManagerFactory.createEntityManager(); 075 this.transaction = this.entityManager.getTransaction(); 076 this.transaction.begin(); 077 } catch (final Exception e) { 078 throw new AppenderLoggingException( 079 "Cannot write logging event or flush buffer; manager cannot create EntityManager or transaction.", e 080 ); 081 } 082 } 083 084 @Override 085 protected void writeInternal(final LogEvent event) { 086 if (!this.isRunning() || this.entityManagerFactory == null || this.entityManager == null 087 || this.transaction == null) { 088 throw new AppenderLoggingException( 089 "Cannot write logging event; JPA manager not connected to the database."); 090 } 091 092 AbstractLogEventWrapperEntity entity; 093 try { 094 entity = this.entityConstructor.newInstance(event); 095 } catch (final Exception e) { 096 throw new AppenderLoggingException("Failed to instantiate entity class [" + this.entityClassName + "].", e); 097 } 098 099 try { 100 this.entityManager.persist(entity); 101 } catch (final Exception e) { 102 if (this.transaction != null && this.transaction.isActive()) { 103 this.transaction.rollback(); 104 this.transaction = null; 105 } 106 throw new AppenderLoggingException("Failed to insert record for log event in JPA manager: " + 107 e.getMessage(), e); 108 } 109 } 110 111 @Override 112 protected void commitAndClose() { 113 try { 114 if (this.transaction != null && this.transaction.isActive()) { 115 this.transaction.commit(); 116 } 117 } catch (final Exception e) { 118 if (this.transaction != null && this.transaction.isActive()) { 119 this.transaction.rollback(); 120 } 121 } finally { 122 this.transaction = null; 123 try { 124 if (this.entityManager != null && this.entityManager.isOpen()) { 125 this.entityManager.close(); 126 } 127 } catch (final Exception e) { 128 LOGGER.warn("Failed to close entity manager while logging event or flushing buffer.", e); 129 } finally { 130 this.entityManager = null; 131 } 132 } 133 } 134 135 /** 136 * Creates a JPA manager for use within the {@link JpaAppender}, or returns a suitable one if it already exists. 137 * 138 * @param name The name of the manager, which should include connection details, entity class name, etc. 139 * @param bufferSize The size of the log event buffer. 140 * @param entityClass The fully-qualified class name of the {@link AbstractLogEventWrapperEntity} concrete 141 * implementation. 142 * @param entityConstructor The one-arg {@link LogEvent} constructor for the concrete entity class. 143 * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events. 144 * @return a new or existing JPA manager as applicable. 145 */ 146 public static JpaDatabaseManager getJPADatabaseManager(final String name, final int bufferSize, 147 final Class<? extends AbstractLogEventWrapperEntity> 148 entityClass, 149 final Constructor<? extends AbstractLogEventWrapperEntity> 150 entityConstructor, 151 final String persistenceUnitName) { 152 153 return AbstractDatabaseManager.getManager( 154 name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY 155 ); 156 } 157 158 /** 159 * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers. 160 */ 161 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { 162 private final Class<? extends AbstractLogEventWrapperEntity> entityClass; 163 private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; 164 private final String persistenceUnitName; 165 166 protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass, 167 final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, 168 final String persistenceUnitName) { 169 super(bufferSize); 170 171 this.entityClass = entityClass; 172 this.entityConstructor = entityConstructor; 173 this.persistenceUnitName = persistenceUnitName; 174 } 175 } 176 177 /** 178 * Creates managers. 179 */ 180 private static final class JPADatabaseManagerFactory implements ManagerFactory<JpaDatabaseManager, FactoryData> { 181 @Override 182 public JpaDatabaseManager createManager(final String name, final FactoryData data) { 183 return new JpaDatabaseManager( 184 name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName 185 ); 186 } 187 } 188}