package org.pentaho.platform.engine.core.system.objfac; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import org.apache.commons.lang.ClassUtils; import org.pentaho.platform.api.engine.IPentahoObjectReference; import org.pentaho.platform.api.engine.IPentahoObjectRegistration; import org.pentaho.platform.api.engine.IPentahoRegistrableObjectFactory; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.ObjectFactoryException; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.objfac.references.SingletonPentahoObjectReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; /** * This class supports the registration of Object implementations as well as {@link IPentahoObjectReference }s at * runtime. * <p/> * PentahoSystem adds an instance of this class to its AggregateObjectFactory and delegates to it for its static * registration methods which shadow those defined in {@link IPentahoRegistrableObjectFactory} * <p/> * Created by nbaker on 2/19/14. */ public class RuntimeObjectFactory implements IPentahoRegistrableObjectFactory { private final Multimap<Class, IPentahoObjectReference<?>> registry = Multimaps.synchronizedSetMultimap( HashMultimap .<Class, IPentahoObjectReference<?>>create() ); public RuntimeObjectFactory() { } /** * {@inheritDoc} */ @Override @SuppressWarnings( "unchecked" ) public <T> IPentahoObjectRegistration registerObject( T obj ) { if ( obj instanceof IPentahoObjectReference ) { throw new IllegalArgumentException( "Object cannot be of type: IPentahoObjectRegistration. Call the appropriate registerReference instead" ); } return registerReference( new SingletonPentahoObjectReference<T>( (Class<T>) obj.getClass(), obj ), Types.ALL ); } /** * {@inheritDoc} */ @Override public <T> IPentahoObjectRegistration registerReference( IPentahoObjectReference<T> reference ) { return registerReference( reference, Types.ALL ); } /** * {@inheritDoc} */ @Override @SuppressWarnings( "unchecked" ) public <T> IPentahoObjectRegistration registerObject( T obj, Types types ) { if ( obj instanceof IPentahoObjectReference ) { throw new IllegalArgumentException( "Object cannot be of type: IPentahoObjectRegistration. Call the appropriate registerReference instead" ); } return registerReference( new SingletonPentahoObjectReference<T>( (Class<T>) obj.getClass(), obj ), types ); } /** * {@inheritDoc} */ @SuppressWarnings( { "unchecked" } ) @Override public <T> IPentahoObjectRegistration registerReference( IPentahoObjectReference<T> reference, Types types ) { List<Class<?>> classesToPublishAs = new ArrayList<Class<?>>(); final Class<?> clazz = reference.getObjectClass(); switch( types ) { case INTERFACES: classesToPublishAs.addAll( ClassUtils.getAllInterfaces( clazz ) ); break; case CLASSES: classesToPublishAs.addAll( ClassUtils.getAllSuperclasses( clazz ) ); classesToPublishAs.add( clazz ); break; case ALL: classesToPublishAs.addAll( ClassUtils.getAllInterfaces( clazz ) ); classesToPublishAs.addAll( ClassUtils.getAllSuperclasses( clazz ) ); classesToPublishAs.add( clazz ); break; } return registerReference( reference, classesToPublishAs.toArray( new Class[ classesToPublishAs.size() ] ) ); } @SuppressWarnings( "unchecked" ) @Override public <T> IPentahoObjectRegistration registerObject( T obj, Class<?>... classes ) { if ( obj instanceof IPentahoObjectReference ) { throw new IllegalArgumentException( "Object cannot be of type: IPentahoObjectRegistration. Call the appropriate registerReference instead" ); } return registerReference( new SingletonPentahoObjectReference<T>( (Class<T>) obj.getClass(), obj ), classes ); } @Override public <T> IPentahoObjectRegistration registerReference( IPentahoObjectReference<T> reference, Class<?>... classes ) { for ( Class<?> aClass : classes ) { registry.get( aClass ).add( reference ); } return new ObjectRegistration( reference, Arrays.asList( classes ) ); } /** * {@inheritDoc} */ @Override public <T> T get( final Class<T> interfaceClass, final IPentahoSession session ) throws ObjectFactoryException { final IPentahoObjectReference<T> objectReference = getObjectReference( interfaceClass, session ); if ( objectReference == null ) { return null; } return objectReference.getObject(); } /** * {@inheritDoc} */ @Override public <T> T get( final Class<T> interfaceClass, final String key, final IPentahoSession session ) throws ObjectFactoryException { IPentahoObjectReference<T> reference = getObjectReference( interfaceClass, session, Collections.singletonMap( "id", key ) ); if ( reference == null ) { // not found by ID, check by class itself ( special behavior for this deprecated method ) reference = getObjectReference( interfaceClass, session, Collections.<String, String>emptyMap() ); if ( reference == null ) { return null; } } return reference.getObject(); } /** * {@inheritDoc} */ @Override public <T> T get( final Class<T> interfaceClass, final IPentahoSession session, final Map<String, String> properties ) throws ObjectFactoryException { List<IPentahoObjectReference<T>> references = getObjectReferences( interfaceClass, session, properties ); if ( references.isEmpty() ) { return null; } return references.get( 0 ).getObject(); } /** * This class cannot respond to a simple key request. False will always be returned. * <p/> * {@inheritDoc} */ @Override public boolean objectDefined( String key ) { return false; } /** * {@inheritDoc} */ @Override public boolean objectDefined( Class<?> clazz ) { return !registry.get( clazz ).isEmpty(); } /** * This class cannot respond to a simple key request. Null will always be returned. * <p/> * {@inheritDoc} */ @Override public Class<?> getImplementingClass( String key ) { return null; } /** * No meaning for this class. No-Op Implementation to satisfy interface. * <p/> * {@inheritDoc} */ @Override public void init( String configFile, Object context ) { } /** * {@inheritDoc} */ @Override public <T> List<T> getAll( final Class<T> interfaceClass, final IPentahoSession session ) throws ObjectFactoryException { return getAll( interfaceClass, session, Collections.<String, String>emptyMap() ); } /** * {@inheritDoc} */ @Override @SuppressWarnings( "unchecked" ) public <T> List<T> getAll( final Class<T> interfaceClass, final IPentahoSession session, final Map<String, String> properties ) throws ObjectFactoryException { List<IPentahoObjectReference<T>> retValReferences = getObjectReferences( interfaceClass, session, properties ); List<T> retVals = new ArrayList<T>(); for ( IPentahoObjectReference ref : retValReferences ) { retVals.add( (T) ref.getObject() ); } return retVals; } /** * {@inheritDoc} */ @Override public <T> IPentahoObjectReference<T> getObjectReference( Class<T> interfaceClass, IPentahoSession curSession ) throws ObjectFactoryException { return getObjectReference( interfaceClass, curSession, Collections.<String, String>emptyMap() ); } /** * {@inheritDoc} */ @Override public <T> IPentahoObjectReference<T> getObjectReference( Class<T> interfaceClass, IPentahoSession curSession, Map<String, String> properties ) throws ObjectFactoryException { final List<IPentahoObjectReference<T>> objectReferences = getObjectReferences( interfaceClass, curSession, properties ); if ( objectReferences.isEmpty() ) { return null; } return objectReferences.get( 0 ); } /** * {@inheritDoc} */ @Override public <T> List<IPentahoObjectReference<T>> getObjectReferences( Class<T> interfaceClass, IPentahoSession curSession ) throws ObjectFactoryException { return getObjectReferences( interfaceClass, curSession, Collections.<String, String>emptyMap() ); } /** * {@inheritDoc} */ @Override public <T> List<IPentahoObjectReference<T>> getObjectReferences( final Class<T> interfaceClass, final IPentahoSession curSession, final Map<String, String> properties ) throws ObjectFactoryException { List<IPentahoObjectReference<T>> retValReferences = new ArrayList<IPentahoObjectReference<T>>(); try { retValReferences = SessionCapturedOperation.execute( curSession, new Callable<List<IPentahoObjectReference<T>>>() { @SuppressWarnings( "unchecked" ) @Override public List<IPentahoObjectReference<T>> call() throws Exception { List<IPentahoObjectReference<?>> iPentahoObjectReferences = getReferencesByQuery( interfaceClass, properties ); final ArrayList<IPentahoObjectReference<T>> retVals = new ArrayList<IPentahoObjectReference<T>>(); if ( !iPentahoObjectReferences.isEmpty() ) { for ( IPentahoObjectReference<?> ref : iPentahoObjectReferences ) { retVals.add( (IPentahoObjectReference<T>) ref ); } } return retVals; } } ); } catch ( Exception e ) { e.printStackTrace(); } Collections.sort( retValReferences ); Collections.reverse( retValReferences ); return retValReferences; } /** * {@inheritDoc} */ @Override public String getName() { return "Runtime Object Factory"; } protected <T> List<IPentahoObjectReference<?>> getReferencesByQuery( Class<T> type, Map<String, String> query ) { Collection<IPentahoObjectReference<?>> iPentahoObjectReferences = registry.get( type ); if ( iPentahoObjectReferences.isEmpty() ) { return Collections.emptyList(); } final ArrayList<IPentahoObjectReference<?>> returnCollection = new ArrayList<IPentahoObjectReference<?>>(); for ( IPentahoObjectReference<?> next : iPentahoObjectReferences ) { if ( query == null || query.isEmpty() ) { returnCollection.add( next ); continue; } final Map<String, Object> attributes = next.getAttributes(); boolean matches = true; for ( Map.Entry<String, String> queryEntry : query.entrySet() ) { if ( !attributes.containsKey( queryEntry.getKey() ) || ! attributes.get( queryEntry.getKey() ) .equals( queryEntry.getValue() ) ) { matches = false; break; } } if( matches ) { returnCollection.add( next ); } } return returnCollection; } /** * Light wrapper around a {@link Callable} which ensures that the Thread-Bound session in {@link PentahoSessionHolder} * is set to that of the specified Session from the ObjectFactory request. */ private static class SessionCapturedOperation { private static <T> T execute( IPentahoSession session, Callable<T> callee ) throws Exception { SessionSwapper.swap( session ); try { return callee.call(); } finally { SessionSwapper.restore(); } } } /** * Captures the Thread-Bound session replacing it with the one provided. Supports restoring the original later. */ private static class SessionSwapper { private static final ThreadLocal<IPentahoSession> originalSessions = new ThreadLocal<IPentahoSession>(); public static void swap( IPentahoSession tempSession ) { IPentahoSession originalSession = PentahoSessionHolder.getSession(); // Capture even if the same to simplify restore originalSessions.set( originalSession ); if ( originalSession != tempSession ) { PentahoSessionHolder.setSession( tempSession ); } } public static void restore() { IPentahoSession orig = originalSessions.get(); PentahoSessionHolder.setSession( orig ); } } /** * Handle returned when an object or reference is registered. Supports de-registration. */ protected class ObjectRegistration implements IPentahoObjectRegistration { private IPentahoObjectReference<?> reference; private List<Class<?>> publishedClasses; public ObjectRegistration( IPentahoObjectReference<?> reference, List<Class<?>> publishedClasses ) { this.reference = reference; this.publishedClasses = publishedClasses; } public IPentahoObjectReference<?> getReference() { return reference; } public List<Class<?>> getPublishedClasses() { return publishedClasses; } @Override public void remove() { for ( Class<?> aClass : publishedClasses ) { registry.get( aClass ).remove( reference ); } } } }