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 boolean shutdownInternal() { 063 boolean closed = true; 064 if (this.entityManager != null || this.transaction != null) { 065 closed &= this.commitAndClose(); 066 } 067 if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { 068 this.entityManagerFactory.close(); 069 } 070 return closed; 071 } 072 073 @Override 074 protected void connectAndStart() { 075 try { 076 this.entityManager = this.entityManagerFactory.createEntityManager(); 077 this.transaction = this.entityManager.getTransaction(); 078 this.transaction.begin(); 079 } catch (final Exception e) { 080 throw new AppenderLoggingException( 081 "Cannot write logging event or flush buffer; manager cannot create EntityManager or transaction.", e 082 ); 083 } 084 } 085 086 @Override 087 protected void writeInternal(final LogEvent event) { 088 if (!this.isRunning() || this.entityManagerFactory == null || this.entityManager == null 089 || this.transaction == null) { 090 throw new AppenderLoggingException( 091 "Cannot write logging event; JPA manager not connected to the database."); 092 } 093 094 AbstractLogEventWrapperEntity entity; 095 try { 096 entity = this.entityConstructor.newInstance(event); 097 } catch (final Exception e) { 098 throw new AppenderLoggingException("Failed to instantiate entity class [" + this.entityClassName + "].", e); 099 } 100 101 try { 102 this.entityManager.persist(entity); 103 } catch (final Exception e) { 104 if (this.transaction != null && this.transaction.isActive()) { 105 this.transaction.rollback(); 106 this.transaction = null; 107 } 108 throw new AppenderLoggingException("Failed to insert record for log event in JPA manager: " + 109 e.getMessage(), e); 110 } 111 } 112 113 @Override 114 protected boolean commitAndClose() { 115 boolean closed = true; 116 try { 117 if (this.transaction != null && this.transaction.isActive()) { 118 this.transaction.commit(); 119 } 120 } catch (final Exception e) { 121 if (this.transaction != null && this.transaction.isActive()) { 122 this.transaction.rollback(); 123 } 124 } finally { 125 this.transaction = null; 126 try { 127 if (this.entityManager != null && this.entityManager.isOpen()) { 128 this.entityManager.close(); 129 } 130 } catch (final Exception e) { 131 logWarn("Failed to close entity manager while logging event or flushing buffer", e); 132 closed = false; 133 } finally { 134 this.entityManager = null; 135 } 136 } 137 return closed; 138 } 139 140 /** 141 * Creates a JPA manager for use within the {@link JpaAppender}, or returns a suitable one if it already exists. 142 * 143 * @param name The name of the manager, which should include connection details, entity class name, etc. 144 * @param bufferSize The size of the log event buffer. 145 * @param entityClass The fully-qualified class name of the {@link AbstractLogEventWrapperEntity} concrete 146 * implementation. 147 * @param entityConstructor The one-arg {@link LogEvent} constructor for the concrete entity class. 148 * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events. 149 * @return a new or existing JPA manager as applicable. 150 */ 151 public static JpaDatabaseManager getJPADatabaseManager(final String name, final int bufferSize, 152 final Class<? extends AbstractLogEventWrapperEntity> 153 entityClass, 154 final Constructor<? extends AbstractLogEventWrapperEntity> 155 entityConstructor, 156 final String persistenceUnitName) { 157 158 return AbstractDatabaseManager.getManager( 159 name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY 160 ); 161 } 162 163 /** 164 * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers. 165 */ 166 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { 167 private final Class<? extends AbstractLogEventWrapperEntity> entityClass; 168 private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; 169 private final String persistenceUnitName; 170 171 protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass, 172 final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, 173 final String persistenceUnitName) { 174 super(bufferSize); 175 176 this.entityClass = entityClass; 177 this.entityConstructor = entityConstructor; 178 this.persistenceUnitName = persistenceUnitName; 179 } 180 } 181 182 /** 183 * Creates managers. 184 */ 185 private static final class JPADatabaseManagerFactory implements ManagerFactory<JpaDatabaseManager, FactoryData> { 186 @Override 187 public JpaDatabaseManager createManager(final String name, final FactoryData data) { 188 return new JpaDatabaseManager( 189 name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName 190 ); 191 } 192 } 193}