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 */
017 package org.apache.logging.log4j.core.appender.db.jpa;
018
019 import java.lang.reflect.Constructor;
020
021 import javax.persistence.EntityManager;
022 import javax.persistence.EntityManagerFactory;
023 import javax.persistence.EntityTransaction;
024 import javax.persistence.Persistence;
025
026 import org.apache.logging.log4j.core.LogEvent;
027 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
028 import org.apache.logging.log4j.core.appender.ManagerFactory;
029 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
030
031 /**
032 * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JPA.
033 */
034 public 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 }