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; 020import javax.persistence.EntityManager; 021import javax.persistence.EntityManagerFactory; 022import javax.persistence.EntityTransaction; 023import javax.persistence.Persistence; 024 025import org.apache.logging.log4j.core.LogEvent; 026import org.apache.logging.log4j.core.appender.AppenderLoggingException; 027import org.apache.logging.log4j.core.appender.ManagerFactory; 028import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; 029 030/** 031 * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JPA. 032 */ 033public final class JPADatabaseManager extends AbstractDatabaseManager { 034 private static final JPADatabaseManagerFactory FACTORY = new JPADatabaseManagerFactory(); 035 036 private final String entityClassName; 037 private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; 038 private final String persistenceUnitName; 039 040 private EntityManagerFactory entityManagerFactory; 041 042 private EntityManager entityManager; 043 private EntityTransaction transaction; 044 045 private JPADatabaseManager(final String name, final int bufferSize, 046 final Class<? extends AbstractLogEventWrapperEntity> entityClass, 047 final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, 048 final String persistenceUnitName) { 049 super(name, bufferSize); 050 this.entityClassName = entityClass.getName(); 051 this.entityConstructor = entityConstructor; 052 this.persistenceUnitName = persistenceUnitName; 053 } 054 055 @Override 056 protected void startupInternal() { 057 this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName); 058 } 059 060 @Override 061 protected void shutdownInternal() { 062 if (this.entityManager != null || this.transaction != null) { 063 this.commitAndClose(); 064 } 065 if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { 066 this.entityManagerFactory.close(); 067 } 068 } 069 070 @Override 071 protected void connectAndStart() { 072 try { 073 this.entityManager = this.entityManagerFactory.createEntityManager(); 074 this.transaction = this.entityManager.getTransaction(); 075 this.transaction.begin(); 076 } catch (Exception e) { 077 throw new AppenderLoggingException( 078 "Cannot write logging event or flush buffer; manager cannot create EntityManager or transaction.", e 079 ); 080 } 081 } 082 083 @Override 084 protected void writeInternal(final LogEvent event) { 085 if (!this.isRunning() || this.entityManagerFactory == null || this.entityManager == null 086 || this.transaction == null) { 087 throw new AppenderLoggingException( 088 "Cannot write logging event; JPA manager not connected to the database."); 089 } 090 091 AbstractLogEventWrapperEntity entity; 092 try { 093 entity = this.entityConstructor.newInstance(event); 094 } catch (final Exception e) { 095 throw new AppenderLoggingException("Failed to instantiate entity class [" + this.entityClassName + "].", e); 096 } 097 098 try { 099 this.entityManager.persist(entity); 100 } catch (final Exception e) { 101 if (this.transaction != null && this.transaction.isActive()) { 102 this.transaction.rollback(); 103 this.transaction = null; 104 } 105 throw new AppenderLoggingException("Failed to insert record for log event in JPA manager: " + 106 e.getMessage(), e); 107 } 108 } 109 110 @Override 111 protected void commitAndClose() { 112 try { 113 if (this.transaction != null && this.transaction.isActive()) { 114 this.transaction.commit(); 115 } 116 } catch (Exception e) { 117 if (this.transaction != null && this.transaction.isActive()) { 118 this.transaction.rollback(); 119 } 120 } finally { 121 this.transaction = null; 122 try { 123 if (this.entityManager != null && this.entityManager.isOpen()) { 124 this.entityManager.close(); 125 } 126 } catch (Exception e) { 127 LOGGER.warn("Failed to close entity manager while logging event or flushing buffer.", e); 128 } finally { 129 this.entityManager = null; 130 } 131 } 132 } 133 134 /** 135 * Creates a JPA manager for use within the {@link JPAAppender}, or returns a suitable one if it already exists. 136 * 137 * @param name The name of the manager, which should include connection details, entity class name, etc. 138 * @param bufferSize The size of the log event buffer. 139 * @param entityClass The fully-qualified class name of the {@link AbstractLogEventWrapperEntity} concrete 140 * implementation. 141 * @param entityConstructor The one-arg {@link LogEvent} constructor for the concrete entity class. 142 * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events. 143 * @return a new or existing JPA manager as applicable. 144 */ 145 public static JPADatabaseManager getJPADatabaseManager(final String name, final int bufferSize, 146 final Class<? extends AbstractLogEventWrapperEntity> 147 entityClass, 148 final Constructor<? extends AbstractLogEventWrapperEntity> 149 entityConstructor, 150 final String persistenceUnitName) { 151 152 return AbstractDatabaseManager.getManager( 153 name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY 154 ); 155 } 156 157 /** 158 * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers. 159 */ 160 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { 161 private final Class<? extends AbstractLogEventWrapperEntity> entityClass; 162 private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; 163 private final String persistenceUnitName; 164 165 protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass, 166 final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, 167 final String persistenceUnitName) { 168 super(bufferSize); 169 170 this.entityClass = entityClass; 171 this.entityConstructor = entityConstructor; 172 this.persistenceUnitName = persistenceUnitName; 173 } 174 } 175 176 /** 177 * Creates managers. 178 */ 179 private static final class JPADatabaseManagerFactory implements ManagerFactory<JPADatabaseManager, FactoryData> { 180 @Override 181 public JPADatabaseManager createManager(final String name, final FactoryData data) { 182 return new JPADatabaseManager( 183 name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName 184 ); 185 } 186 } 187}