/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.entities; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference; import javax.annotation.Nullable; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; import javax.persistence.Persistence; import javax.persistence.PersistenceContext; import org.apache.log4j.Logger; import org.hibernate.cfg.Configuration; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.bootstrap.BootstrapException; import com.eucalyptus.bootstrap.Bootstrapper; import com.eucalyptus.bootstrap.DatabaseInfo; import com.eucalyptus.bootstrap.Provides; import com.eucalyptus.bootstrap.RunDuring; import com.eucalyptus.bootstrap.ServiceJarDiscovery; import com.eucalyptus.component.ComponentId; import com.eucalyptus.component.ComponentIds; import com.eucalyptus.component.annotation.DatabaseNamingStrategy; import com.eucalyptus.component.annotation.RemotablePersistence; import com.eucalyptus.empyrean.Empyrean; import com.eucalyptus.entities.impl.EucalyptusPersistenceProvider; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.scripting.Groovyness; import com.eucalyptus.system.Ats; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.LogUtil; import com.eucalyptus.util.Strings; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; @SuppressWarnings( "unchecked" ) public class PersistenceContexts { private static Logger LOG = Logger.getLogger( PersistenceContexts.class ); private static Long MAX_FAIL_SECONDS = 60L; //TODO:GRZE:@Configurable private static final int MAX_EMF_RETRIES = 20; private static AtomicStampedReference<Long> failCount = new AtomicStampedReference<>( 0L, 0 ); private static final ArrayListMultimap<String, Class<?>> entities = ArrayListMultimap.create( ); private static final ArrayListMultimap<String, Class<?>> remotableEntities = ArrayListMultimap.create( ); private static final List<Class> sharedEntities = Lists.newArrayList( ); private static Map<String, EntityManagerFactoryImpl> emf = new ConcurrentSkipListMap<>( ); private static Map<String, PersistenceContextConfiguration> pcc = new ConcurrentHashMap<>( ); @Provides( Empyrean.class ) @RunDuring( Bootstrap.Stage.PersistenceInit ) public static class PersistenceContextBootstrapper extends Bootstrapper.Simple { @Override public boolean load() throws Exception { Groovyness.run( "setup_persistence.groovy" ); return true; } } /** * Interface for interception of persistence context lookup. * <p/> * <p>The lookup method will be invoked prior to creation of * a persistence context. The lookup method is also invoked * for each subsequent (cached) lookup of the context.</p> */ public static interface PersistenceContextEventInterceptor { void onLookup(); void onConnectionError(); } public static class PersistenceContextEventInterceptorDiscovery extends ServiceJarDiscovery { static final List<PersistenceContextEventInterceptor> interceptors = new CopyOnWriteArrayList<PersistenceContextEventInterceptor>( ); static final PersistenceContextEventInterceptor dispatcher = new PersistenceContextEventInterceptor( ) { @Override public void onLookup() { for ( final PersistenceContextEventInterceptor interceptor : interceptors ) { interceptor.onLookup( ); } } @Override public void onConnectionError() { for ( final PersistenceContextEventInterceptor interceptor : interceptors ) { interceptor.onConnectionError( ); } } }; static PersistenceContextEventInterceptor dispatcher() { return dispatcher; } @Override public boolean processClass( final Class candidate ) throws Exception { if ( PersistenceContextEventInterceptor.class.isAssignableFrom( candidate ) && Modifier.isPublic( candidate.getModifiers( ) ) ) { interceptors.add( ( (Class<PersistenceContextEventInterceptor>) candidate ).newInstance( ) ); return true; } return false; } @Override public Double getPriority() { return 1.0d; } } public static boolean isPersistentClass( Class candidate ) { return isSharedEntityClass( candidate ) || isEntityClass( candidate ); } public static boolean isSharedEntityClass( Class candidate ) { return Ats.from( candidate ).has( MappedSuperclass.class ) || Ats.from( candidate ).has( Embeddable.class ); } private static boolean isRemotable( Class entity ) { return Ats.from( entity ).has( RemotablePersistence.class ); } public static boolean isEntityClass( Class candidate ) { if ( Ats.from( candidate ).has( Entity.class ) ) { if ( !Ats.from( candidate ).has( PersistenceContext.class ) ) { throw Exceptions.toUndeclared( "Database entity does not have required @PersistenceContext annotation: " + candidate.getCanonicalName( ) ); } else { return true; } } else { return false; } } public static DatabaseNamingStrategy getNamingStrategy( final String context ) { DatabaseNamingStrategy strategy = DatabaseNamingStrategy.defaultStrategy( ); try { final ComponentId componentId = ComponentIds.lookup( Strings.trimPrefix( "eucalyptus_", context ) ); strategy = componentId.getDatabaseNamingStrategy( ); } catch ( final NoSuchElementException e ) { // use default } return DatabaseNamingStrategy.overrideStrategy( strategy ); } public static Function<String, String> toDatabaseName() { return PersistenceContextStringFunctions.CONTEXT_TO_DATABASE; } public static Function<String, String> toSchemaName() { return PersistenceContextStringFunctions.CONTEXT_TO_SCHEMA; } static void addEntity( Class entity ) { if ( !isDuplicate( entity ) ) { String ctxName = Ats.from( entity ).get( PersistenceContext.class ).name( ); EventRecord.here( PersistenceContextDiscovery.class, EventType.PERSISTENCE_ENTITY_REGISTERED, ctxName, entity.getCanonicalName( ) ).trace( ); if ( isRemotable( entity ) ) remotableEntities.put( ctxName, entity ); else entities.put( ctxName, entity ); } } static void addSharedEntity( Class entity ) { if ( !isDuplicate( entity ) ) { EventRecord.here( PersistenceContextDiscovery.class, EventType.PERSISTENCE_ENTITY_REGISTERED, "shared", entity.getCanonicalName( ) ).trace( ); sharedEntities.add( entity ); } } private static boolean isDuplicate( Class entity ) { PersistenceContext ctx = Ats.from( entity ).get( PersistenceContext.class ); if ( Ats.from( entity ).has( MappedSuperclass.class ) || Ats.from( entity ).has( Embeddable.class ) ) { return false; } else if ( ctx == null || ctx.name( ) == null ) { RuntimeException ex = new RuntimeException( "Failed to register broken entity class: " + entity.getCanonicalName( ) + ". Ensure that the class has a well-formed @PersistenceContext annotation." ); LOG.error( ex, ex ); return false; } else if ( sharedEntities.contains( entity ) ) { Class old = sharedEntities.get( sharedEntities.indexOf( entity ) ); LOG.error( "Duplicate entity definition detected: " + entity.getCanonicalName( ) ); LOG.error( "=> OLD: " + old.getProtectionDomain( ).getCodeSource( ).getLocation( ) ); LOG.error( "=> NEW: " + entity.getProtectionDomain( ).getCodeSource( ).getLocation( ) ); throw BootstrapException.throwFatal( "Duplicate entity definition in shared entities: " + entity.getCanonicalName( ) + ". See error logs for details." ); } else if ( entities.get( ctx.name( ) ) != null && entities.get( ctx.name( ) ).contains( entity ) ) { List<Class<?>> context = entities.get( ctx.name( ) ); Class old = context.get( context.indexOf( entity ) ); LOG.error( "Duplicate entity definition detected: " + entity.getCanonicalName( ) ); LOG.error( "=> OLD: " + old.getProtectionDomain( ).getCodeSource( ).getLocation( ) ); LOG.error( "=> NEW: " + entity.getProtectionDomain( ).getCodeSource( ).getLocation( ) ); throw BootstrapException.throwFatal( "Duplicate entity definition in '" + ctx.name( ) + "': " + entity.getCanonicalName( ) + ". See error logs for details." ); } else { return false; } } public static EntityManagerFactoryImpl registerPersistenceContext( final PersistenceContextConfiguration config ) { final String persistenceContext = config.getName( ); if ( !emf.containsKey( persistenceContext ) ) { try { LOG.trace( "-> Setting up persistence context for: " + persistenceContext ); pcc.put( persistenceContext, config ); final EucalyptusPersistenceProvider provider = new EucalyptusPersistenceProvider( ); EntityManagerFactoryImpl entityManagerFactory = (EntityManagerFactoryImpl) provider.createEntityManagerFactory( persistenceContext, config.getProperties( ) ); LOG.trace( LogUtil.subheader( LogUtil.dumpObject( config ) ) ); emf.put( persistenceContext, entityManagerFactory ); LOG.info( "-> Setup done for persistence context: " + persistenceContext ); } catch ( Exception ex ) { LOG.error( "-> Error in persistence context setup: " + persistenceContext, ex ); } } return emf.get( persistenceContext ); } public static Configuration getConfiguration( final PersistenceContextConfiguration config ) { final EucalyptusPersistenceProvider provider = new EucalyptusPersistenceProvider( ); if ( !pcc.containsKey( config.getName( ) ) ) { pcc.put( config.getName( ), config ); } return provider.getConfiguration( config.getName( ), config.getProperties( ) ); } public static void flush( String ctx ) { emf.get( ctx ).getCache( ).evictAll( ); } public static void deregisterPersistenceContext( final String persistenceContext ) { if ( !emf.containsKey( persistenceContext ) ) return; final EntityManagerFactoryImpl emfactory = emf.remove( persistenceContext ); if ( emfactory != null && emfactory.isOpen( ) ) { try { if ( emfactory.getCache( ) != null ) emfactory.getCache( ).evictAll( ); emfactory.close( ); LOG.info( "Closed entity manager factory for " + persistenceContext ); } catch ( final Exception ex ) { LOG.warn( "Failed to close entity manager factory", ex ); } } } public static List<String> list() { final List<String> persistences = Lists.newArrayList( entities.keySet( ) ); return persistences; } public static List<String> listRemotable() { return Lists.newArrayList( remotableEntities.keySet( ) ); } public static boolean remoteConnected() { for ( final String remoteContext : listRemotable( ) ) { if ( emf.containsKey( remoteContext ) ) return true; } return false; } public static String toRemoteDatabaseName( final String persistenceContext ) { if ( "localhost".equals( DatabaseInfo.getDatabaseInfo( ).getAppendOnlyHost( ) ) ) { return PersistenceContexts.toDatabaseName( ).apply( persistenceContext ); } else { return persistenceContext; } } public static String toRemoteSchemaName( final String persistenceContext ) { if ( "localhost".equals( DatabaseInfo.getDatabaseInfo( ).getAppendOnlyHost( ) ) ) { return PersistenceContexts.toSchemaName( ).apply( persistenceContext ); } else { return null; } } public static List<Class<?>> listEntities( String persistenceContext ) { final List<Class<?>> ctxEntities = Lists.newArrayList( ); if ( entities.containsKey( persistenceContext ) ) { ctxEntities.addAll( Lists.newArrayList( entities.get( persistenceContext ) ) ); } if ( remotableEntities.containsKey( persistenceContext ) ) { ctxEntities.addAll( Lists.newArrayList( remotableEntities.get( persistenceContext ) ) ); } Collections.sort( ctxEntities, Ordering.usingToString( ) ); return ctxEntities; } public static List<AuxiliaryDatabaseObject> listAuxiliaryDatabaseObjects( String persistenceContext ) { final List<AuxiliaryDatabaseObject> ados = Lists.newArrayList( ); for ( final Class entityClass : listEntities( persistenceContext ) ) { final AuxiliaryDatabaseObjects adosAnno = Ats.from( entityClass ).get( AuxiliaryDatabaseObjects.class ); if ( adosAnno != null ) { ados.addAll( Arrays.asList( adosAnno.value( ) ) ); } } return ados; } public static PersistenceContextConfiguration getConfiguration( String persistenceContext ) { return pcc.get( persistenceContext ); } private static void touchDatabase( ) { long failInterval = System.currentTimeMillis( ) - failCount.getReference( ); if ( MAX_FAIL_SECONDS * 1000L > failInterval ) { LOG.fatal( LogUtil.header( "Database connection failure time limit reached (" + MAX_FAIL_SECONDS + " seconds): HUPping the system." ) ); } else if ( failCount.getStamp( ) > 0 ) { LOG.warn( "Found database connection errors: # " + failCount.getStamp( ) + " over the last " + failInterval + " seconds." ); } } @SuppressWarnings( "deprecation" ) public static EntityManagerFactoryImpl getEntityManagerFactory( final String persistenceContext ) { PersistenceContextEventInterceptorDiscovery.dispatcher().onLookup(); if ( emf.containsKey( persistenceContext ) ) { return emf.get( persistenceContext ); } else { for ( int i = 0; i < MAX_EMF_RETRIES; ++i ) { if ( emf.containsKey( persistenceContext ) ) { return emf.get( persistenceContext ); } Exceptions.trace( persistenceContext + ": Persistence context has not been configured yet." + " (see debug logs for details)" + "\nThe available contexts are: \n" + Joiner.on( "\n" ).join( emf.keySet( ) ) ); try { TimeUnit.MILLISECONDS.sleep( 100 ); } catch ( InterruptedException ex ) { throw Exceptions.toUndeclared( Exceptions.maybeInterrupted( ex ) ); } } } throw Exceptions.error( "Failed to lookup persistence context after " + MAX_EMF_RETRIES + " tries.\n" ); } public static void shutdown( ) { for ( String ctx : emf.keySet( ) ) { EntityManagerFactoryImpl em = emf.remove( ctx ); if ( em.isOpen( ) ) { LOG.info( "Closing persistence context: " + ctx ); em.close( ); } else { LOG.info( "Closing persistence context: " + ctx + " (found it closed already)" ); } } } private enum PersistenceContextStringFunctions implements Function<String,String> { CONTEXT_TO_DATABASE { @Nullable @Override public String apply( @Nullable final String context ) { return PersistenceContexts.getNamingStrategy( context ).getDatabaseName( context ); } }, CONTEXT_TO_SCHEMA { @Nullable @Override public String apply( @Nullable final String context ) { return PersistenceContexts.getNamingStrategy( context ).getSchemaName( context ); } }, } }