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}