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    }