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    import javax.persistence.EntityManager;
021    import javax.persistence.EntityManagerFactory;
022    import javax.persistence.EntityTransaction;
023    import javax.persistence.Persistence;
024    
025    import org.apache.logging.log4j.core.LogEvent;
026    import org.apache.logging.log4j.core.appender.ManagerFactory;
027    import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
028    
029    /**
030     * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JPA.
031     */
032    public final class JPADatabaseManager extends AbstractDatabaseManager {
033        private static final JPADatabaseManagerFactory FACTORY = new JPADatabaseManagerFactory();
034    
035        private final String entityClassName;
036        private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor;
037        private final String persistenceUnitName;
038    
039        private EntityManager entityManager;
040        private EntityManagerFactory entityManagerFactory;
041        private EntityTransaction transaction;
042    
043        private JPADatabaseManager(final String name, final int bufferSize,
044                                   final Class<? extends AbstractLogEventWrapperEntity> entityClass,
045                                   final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor,
046                                   final String persistenceUnitName) {
047            super(name, bufferSize);
048            this.entityClassName = entityClass.getName();
049            this.entityConstructor = entityConstructor;
050            this.persistenceUnitName = persistenceUnitName;
051        }
052    
053        @Override
054        protected void connectInternal() {
055            this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName);
056            this.entityManager = this.entityManagerFactory.createEntityManager();
057            this.transaction = this.entityManager.getTransaction();
058        }
059    
060        @Override
061        protected void disconnectInternal() {
062            this.transaction = null;
063    
064            if (this.entityManager != null && this.entityManager.isOpen()) {
065                this.entityManager.close();
066            }
067    
068            if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) {
069                this.entityManagerFactory.close();
070            }
071        }
072    
073        @Override
074        protected void writeInternal(final LogEvent event) {
075            if (!this.isConnected() || this.transaction == null || this.entityManager == null
076                    || this.entityManagerFactory == null) {
077                LOGGER.error("Cannot write logging event; manager [{}] not connected to the database.", this.getName());
078                return;
079            }
080    
081            AbstractLogEventWrapperEntity entity;
082            try {
083                entity = this.entityConstructor.newInstance(event);
084            } catch (final Exception e) {
085                LOGGER.error("Failed to instantiate entity class {}.", this.entityClassName, e);
086                return;
087            }
088    
089            try {
090                this.transaction.begin();
091                this.entityManager.persist(entity);
092                this.transaction.commit();
093            } catch (final Exception e) {
094                LOGGER.error("Failed to persist log event entity.", e);
095                this.transaction.rollback();
096            }
097        }
098    
099        /**
100         * Creates a JPA manager for use within the {@link JPAAppender}, or returns a suitable one if it already exists.
101         *
102         * @param name The name of the manager, which should include connection details, entity class name, etc.
103         * @param bufferSize The size of the log event buffer.
104         * @param entityClass The fully-qualified class name of the {@link AbstractLogEventWrapperEntity} concrete
105         *                    implementation.
106         * @param entityConstructor The one-arg {@link LogEvent} constructor for the concrete entity class.
107         * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events.
108         * @return a new or existing JPA manager as applicable.
109         */
110        public static JPADatabaseManager getJPADatabaseManager(final String name, final int bufferSize,
111                                                               final Class<? extends AbstractLogEventWrapperEntity>
112                                                                       entityClass,
113                                                               final Constructor<? extends AbstractLogEventWrapperEntity>
114                                                                       entityConstructor,
115                                                               final String persistenceUnitName) {
116    
117            return AbstractDatabaseManager.getManager(
118                    name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY
119            );
120        }
121    
122        /**
123         * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers.
124         */
125        private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
126            private final Class<? extends AbstractLogEventWrapperEntity> entityClass;
127            private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor;
128            private final String persistenceUnitName;
129    
130            protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass,
131                                  final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor,
132                                  final String persistenceUnitName) {
133                super(bufferSize);
134    
135                this.entityClass = entityClass;
136                this.entityConstructor = entityConstructor;
137                this.persistenceUnitName = persistenceUnitName;
138            }
139        }
140    
141        /**
142         * Creates managers.
143         */
144        private static final class JPADatabaseManagerFactory implements ManagerFactory<JPADatabaseManager, FactoryData> {
145            @Override
146            public JPADatabaseManager createManager(final String name, final FactoryData data) {
147                return new JPADatabaseManager(
148                        name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName
149                );
150            }
151        }
152    }