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