/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, version 2 as published by the Free Software * Foundation. * * You should have received a copy of the GNU General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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. * * * Copyright 2006 - 2013 Pentaho Corporation. All rights reserved. */ package org.pentaho.platform.engine.core.system.objfac; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.platform.api.engine.IPentahoInitializer; import org.pentaho.platform.api.engine.IPentahoObjectFactory; import org.pentaho.platform.api.engine.IPentahoObjectReference; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.ObjectFactoryException; import org.pentaho.platform.engine.core.messages.Messages; import org.pentaho.platform.engine.core.system.StandaloneSession; import org.pentaho.platform.engine.core.system.objfac.spring.SpringScopeSessionHolder; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; /** * Framework for Spring-based object factories. Subclasses are required only to implement the init method, which is * responsible for setting the {@link ApplicationContext}. * <p/> * A note on creation and management of objects: Object creation and scoping is handled by Spring with one exception: in * the case of a {@link StandaloneSession}. Spring's session scope relates a bean to an javax.servlet.http.HttpSession, * and as such it does not know about custom sessions. The correct approach to solve this problem is to write a custom * Spring scope (called something like "pentahosession"). Unfortunately, we cannot implement a custom scope to handle * the {@link StandaloneSession} because the custom scope would not be able to access it. There is currently no way to * statically obtain a reference to a pentaho session. So we are left with using custom logic in this factory to execute * a different non-Spring logic path when the IPentahoSession is of type StandaloneSession. * <p/> * * @author Aaron Phillips * @see IPentahoObjectFactory */ public abstract class AbstractSpringPentahoObjectFactory implements IPentahoObjectFactory { protected ConfigurableApplicationContext beanFactory; protected static final Log logger = LogFactory.getLog( AbstractSpringPentahoObjectFactory.class ); protected static final String PRIORITY = "priority"; private BeanDefinitionPriorityComparitor priorityComparitor = new BeanDefinitionPriorityComparitor(); private String name; protected AbstractSpringPentahoObjectFactory() { } protected AbstractSpringPentahoObjectFactory( final String name ) { this.name = name; } /** * @see IPentahoObjectFactory#get(Class, IPentahoSession) */ public <T> T get( Class<T> interfaceClass, final IPentahoSession session ) throws ObjectFactoryException { return get( interfaceClass, null, session ); } /** * @see IPentahoObjectFactory#get(Class, String, IPentahoSession) */ public <T> T get( Class<T> interfaceClass, String key, final IPentahoSession session ) throws ObjectFactoryException { return retreiveObject( interfaceClass, key, session, null ); } public <T> T get( Class<T> interfaceClass, IPentahoSession session, Map<String, String> props ) throws ObjectFactoryException { return retreiveObject( interfaceClass, null, session, props ); } @Override public <T> List<T> getAll( Class<T> interfaceClass, IPentahoSession curSession, Map<String, String> properties ) throws ObjectFactoryException { return retreiveObjects( interfaceClass, curSession, properties ); } @Override public <T> List<T> getAll( Class<T> interfaceClass, IPentahoSession curSession ) throws ObjectFactoryException { return retreiveObjects( interfaceClass, curSession, null ); } protected Object instanceClass( String simpleName ) throws ObjectFactoryException { return instanceClass( simpleName, null ); } protected Object instanceClass( String simpleName, String key ) throws ObjectFactoryException { Object object = null; try { if ( beanFactory.containsBean( simpleName ) ) { object = beanFactory.getType( simpleName ).newInstance(); } else if ( key != null ) { object = beanFactory.getType( key ).newInstance(); } } catch ( Exception e ) { String msg = Messages.getInstance() .getString( "AbstractSpringPentahoObjectFactory.WARN_FAILED_TO_CREATE_OBJECT", key ); //$NON-NLS-1$ throw new ObjectFactoryException( msg, e ); } return object; } protected Object instanceClass( Class<?> interfaceClass, String key ) throws ObjectFactoryException { Object object = null; try { if ( key != null ) { object = beanFactory.getType( key ).newInstance(); } else { // No published beans by this type, try the interface simplename itself as the key (legacy behavior) object = beanFactory.getType( interfaceClass.getSimpleName() ).newInstance(); } } catch ( Exception e ) { String msg = Messages.getInstance() .getString( "AbstractSpringPentahoObjectFactory.WARN_FAILED_TO_CREATE_OBJECT", key ); //$NON-NLS-1$ throw new ObjectFactoryException( msg, e ); } return object; } private <T> T retrieveViaSpring( Class<T> interfaceClass ) throws ObjectFactoryException { return retrieveViaSpring( interfaceClass, null ); } private <T> T retrieveViaSpring( Class<T> interfaceClass, Map<String, String> props ) throws ObjectFactoryException { Object object = null; try { String beanName = interfaceClass.getSimpleName(); if ( !beanFactory.getBeanFactory().containsBean( beanName ) ) { throw new IllegalStateException( "No bean found for given type" ); } object = beanFactory.getBean( beanName ); } catch ( Throwable t ) { String msg = Messages.getInstance().getString( "AbstractSpringPentahoObjectFactory.WARN_FAILED_TO_RETRIEVE_OBJECT", interfaceClass.getSimpleName() ); //$NON-NLS-1$ throw new ObjectFactoryException( msg, t ); } // Sanity check if ( interfaceClass.isAssignableFrom( object.getClass() ) == false ) { throw new IllegalStateException( "Object retrived from Spring not expected type: " + interfaceClass.getSimpleName() ); } return (T) object; } private BeanDefinition getBeanDefinitionFromFactory( final String name ) { return beanFactory.getBeanFactory().getBeanDefinition( name ); } protected Object retrieveViaSpring( String beanId ) throws ObjectFactoryException { Object object; try { object = beanFactory.getBean( beanId ); } catch ( Throwable t ) { String msg = Messages.getInstance() .getString( "AbstractSpringPentahoObjectFactory.WARN_FAILED_TO_RETRIEVE_OBJECT", beanId ); //$NON-NLS-1$ throw new ObjectFactoryException( msg, t ); } return object; } private <T> T retreiveObject( Class<T> interfaceClass, String key, IPentahoSession session, Map<String, String> props ) throws ObjectFactoryException { if ( logger.isDebugEnabled() ) { // cannot access logger here since this object factory provides the logger logger .debug( "Attempting to get an instance of [" + interfaceClass.getSimpleName() + "] while in session [" + session + "]" ); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ } // Set the Thread Context Classloader to that of the classloader who loaded this class. ClassLoader originalClassLoader = null; Object object; try { originalClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader( getClass().getClassLoader() ); if ( session instanceof StandaloneSession ) { // first ask Spring for the object, if it is session scoped it will fail // since Spring doesn't know about StandaloneSessions // Save the session off to support Session and Request scope. SpringScopeSessionHolder.SESSION.set( session ); try { if ( key != null ) { // if they want it by id, look for it that way first object = retrieveViaSpring( key ); } else { object = retrieveViaSpring( interfaceClass, props ); } } catch ( Throwable t ) { // Spring could not create the object, perhaps due to session scoping, let's try // retrieving it from our internal session map logger.debug( "Retrieving object from Pentaho session map (not Spring)." ); //$NON-NLS-1$ try { object = session.getAttribute( interfaceClass.getSimpleName() ); if ( ( object == null ) ) { // our internal session map doesn't have it, let's create it object = instanceClass( interfaceClass, key ); session.setAttribute( interfaceClass.getSimpleName(), object ); } } catch ( Throwable tt ) { String msg = Messages.getInstance().getString( "AbstractSpringPentahoObjectFactory.WARN_FAILED_TO_RETRIEVE_OBJECT", interfaceClass.getSimpleName() ); //$NON-NLS-1$ throw new ObjectFactoryException( msg, tt ); } } } else { // be sure to clear out any session held. SpringScopeSessionHolder.SESSION.set( null ); // Spring can handle the object retrieval since we are not dealing with StandaloneSession if ( key != null ) { // if they want it by id, look for it that way first object = retrieveViaSpring( key ); } else { object = retrieveViaSpring( interfaceClass, props ); } } } finally { if ( originalClassLoader != null ) { Thread.currentThread().setContextClassLoader( originalClassLoader ); } } if ( logger.isDebugEnabled() ) { logger .debug( " Got an instance of [" + interfaceClass.getSimpleName() + "]: " + object ); //$NON-NLS-1$ //$NON-NLS-2$ } if ( object instanceof IPentahoInitializer ) { ( (IPentahoInitializer) object ).init( session ); } return (T) object; } protected <T> List<T> retreiveObjects( Class<T> type, final IPentahoSession session, Map<String, String> properties ) throws ObjectFactoryException { if ( logger.isDebugEnabled() ) { // cannot access logger here since this object factory provides the logger logger.debug( "Attempting to get an instance of [" + type.getSimpleName() + "] while in session [" + session + "]" ); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ } T object = retrieveViaSpring( type ); if ( object != null && logger.isDebugEnabled() ) { logger.debug( " Got an instance of [" + type.getSimpleName() + "]: " + object ); //$NON-NLS-1$ //$NON-NLS-2$ } return Collections.singletonList( object ); } /** * @see IPentahoObjectFactory#objectDefined(String) */ public boolean objectDefined( String key ) { return beanFactory.containsBean( key ); } /** * @param clazz Interface or class literal to search for * @return true if a definition exists */ @Override public boolean objectDefined( Class<?> clazz ) { return beanFactory.containsBean( clazz.getSimpleName() ); } /** * @see IPentahoObjectFactory#getImplementingClass(String) */ @SuppressWarnings( "unchecked" ) public Class getImplementingClass( String key ) { return beanFactory.getType( key ); } protected void setBeanFactory( ConfigurableApplicationContext context ) { beanFactory = context; } @Override public <T> IPentahoObjectReference<T> getObjectReference( Class<T> clazz, IPentahoSession curSession ) throws ObjectFactoryException { return getObjectReference( clazz, curSession, null ); } @Override public <T> IPentahoObjectReference<T> getObjectReference( Class<T> clazz, IPentahoSession curSession, Map<String, String> properties ) throws ObjectFactoryException { return null; } @Override public <T> List<IPentahoObjectReference<T>> getObjectReferences( Class<T> interfaceClass, IPentahoSession curSession ) throws ObjectFactoryException { return getObjectReferences( interfaceClass, curSession, null ); } @Override public <T> List<IPentahoObjectReference<T>> getObjectReferences( Class<T> interfaceClass, IPentahoSession curSession, Map<String, String> properties ) throws ObjectFactoryException { return Collections.emptyList(); } @Override public String getName() { return name; } /** * Used to order the lists of implementations based on their priority */ protected class BeanDefinitionPriorityComparitor implements Comparator<BeanDefinitionNamePair> { @Override public int compare( BeanDefinitionNamePair beanDefinitionNamePair, BeanDefinitionNamePair beanDefinitionNamePair1 ) { int pri1 = computePriority( beanDefinitionNamePair.definition ); int pri2 = computePriority( beanDefinitionNamePair1.definition ); if ( pri1 == pri2 ) { return 0; } if ( pri1 < pri2 ) { return 1; } return -1; } private int computePriority( BeanDefinition ref ) { if ( ref == null || ref.getAttribute( PRIORITY ) == null ) { // return default return DEFAULT_PRIORTIY; } try { int val = Integer.parseInt( ref.getAttribute( PRIORITY ).toString() ); return val; } catch ( NumberFormatException e ) { logger .error( "bean of type " + ref.getBeanClassName() + " has an invalid priority value, only numeric allowed" ); // return default return DEFAULT_PRIORTIY; } } } /** * Struct class used internally to maintain a mapping between bean name and definition */ protected static class BeanDefinitionNamePair { public String name; public BeanDefinition definition; public BeanDefinitionNamePair( String name, BeanDefinition definition ) { this.definition = definition; this.name = name; } } @Override public int hashCode() { return this.beanFactory.getBeanFactory().hashCode(); } @Override public boolean equals( Object o ) { if ( this == o ) { return true; } if ( !( o instanceof AbstractSpringPentahoObjectFactory ) ) { return false; } AbstractSpringPentahoObjectFactory that = (AbstractSpringPentahoObjectFactory) o; if ( !beanFactory.equals( that.beanFactory ) ) { return false; } return true; } }