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