/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.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 Lesser General Public License for more details.
*
* Copyright 2013 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.engine.core.system.objfac;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
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.system.osgi.OSGIUtils;
import org.pentaho.platform.engine.core.system.osgi.OsgiPentahoObjectReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This IPentahoObjectFactory implementation looks up objects in a configured OSGI BundleContext.
* <p/>
* Parameterized get() calls will have their values converted to an OSGI service {@link org.osgi.framework.Filter} with
* all parameters combined with boolean "&".
* <p/>
* User: nbaker Date: 10/31/13 Time: 11:43 AM
*/
@SuppressWarnings( "unchecked" )
public class OSGIObjectFactory implements IPentahoObjectFactory {
private BundleContext context;
Logger log = LoggerFactory.getLogger( OSGIObjectFactory.class );
public static final String REFERENCE_CLASS = "reference_class";
public OSGIObjectFactory( final BundleContext context ) {
this.context = context;
}
public <T> T get( Class<T> tClass, IPentahoSession session ) throws ObjectFactoryException {
return get( tClass, null, session );
}
@Override
public <T> T get( Class<T> interfaceClass, String key, IPentahoSession session ) throws ObjectFactoryException {
Map map = ( key != null ) ? Collections.singletonMap( "id", key ) : Collections.emptyMap();
return (T) get( interfaceClass, session, map );
}
@Override
public <T> T get( Class<T> interfaceClass, IPentahoSession session, Map<String, String> properties )
throws ObjectFactoryException {
if ( isBundleContextValid() == false ) {
return null;
}
try {
Map<String, String> props = new HashMap<String, String>( );
if( properties != null ){
props.putAll( properties);
}
props.put( REFERENCE_CLASS, interfaceClass.getName() );
Collection<ServiceReference<IPentahoObjectReference>> serviceReferences = this.context
.getServiceReferences( IPentahoObjectReference.class, OSGIUtils.createFilter( props ) );
if( serviceReferences != null && serviceReferences.size() > 0 ){
IPentahoObjectReference<T> obj = context.getService( serviceReferences.iterator().next() );
return obj.getObject();
}
} catch ( InvalidSyntaxException e ) {
log.debug( "Error retrieving from OSGI as ServiceReference, will try as bare type", e );
}
String filter = OSGIUtils.createFilter( properties );
try {
Collection<ServiceReference<T>> refs = context.getServiceReferences( interfaceClass, filter );
ServiceReference ref;
if ( refs != null && refs.size() > 0 ) {
ref = refs.toArray( new ServiceReference[ refs.size() ] )[ 0 ];
} else {
ref = context.getServiceReference( "" + interfaceClass.getName() );
}
if ( ref == null ) {
log.info( "\n\nOSGI: did not find object: " + interfaceClass.getName() );
return null;
}
Object obj = context.getService( ref );
if ( obj instanceof IPentahoInitializer ) {
( (IPentahoInitializer) obj ).init( session );
}
return (T) obj;
} catch ( InvalidSyntaxException e ) {
log.error( "Error retrieving from OSGI ObjectFactory", e );
}
return null;
}
@Override
public boolean objectDefined( String clazz ) {
if ( clazz == null ) {
throw new IllegalStateException( "Class is null" );
}
if ( isBundleContextValid() == false ) {
return false;
}
ServiceReference ref = context.getServiceReference( clazz );
return ref != null;
}
@Override
public boolean objectDefined( Class<?> clazz ) {
if ( clazz == null ) {
throw new IllegalStateException( "Class is null" );
}
try {
return getObjectReference( clazz, null ) != null;
} catch (ObjectFactoryException e) {
return false;
}
}
@Override
public Class<?> getImplementingClass( String key ) {
throw new UnsupportedOperationException( "OSGI Object Factory does not support this method" );
}
@Override
public void init( String configFile, Object context ) {
// No op
}
@Override
public <T> List<T> getAll( Class<T> interfaceClass, IPentahoSession curSession ) throws ObjectFactoryException {
return getAll( interfaceClass, curSession, Collections.<String, String>emptyMap() );
}
@Override
public <T> List<T> getAll( Class<T> interfaceClass, IPentahoSession session, Map<String, String> properties )
throws ObjectFactoryException {
if ( isBundleContextValid() == false ) {
return null;
}
List<T> returnList = new ArrayList<T>();
// make sure we check by reference first
if( properties == null || !properties.containsKey(REFERENCE_CLASS) ) {
Map<String, String> props = new HashMap<String, String>();
if (properties != null) {
props.putAll(properties);
}
props.put(REFERENCE_CLASS, interfaceClass.getName());
List<IPentahoObjectReference> all = getAll(IPentahoObjectReference.class, session, props);
if( all != null ){
for (IPentahoObjectReference iPentahoObjectReference : all) {
returnList.add((T) iPentahoObjectReference.getObject());
}
}
}
String filter = OSGIUtils.createFilter(properties);
try {
Collection<ServiceReference<T>> refs = context.getServiceReferences( interfaceClass, filter );
if ( refs == null || refs.size() == 0 ) {
log.info( "\n\nOSGI: did not find object: " + interfaceClass.getName() );
return returnList;
}
for ( ServiceReference ref : refs ) {
T obj = (T) context.getService( ref );
if ( obj instanceof IPentahoInitializer ) {
( (IPentahoInitializer) obj ).init( session );
}
returnList.add( obj );
}
return returnList;
} catch ( InvalidSyntaxException e ) {
e.printStackTrace();
}
return returnList;
}
@Override
public <T> IPentahoObjectReference<T> getObjectReference( Class<T> interfaceClass, IPentahoSession curSession )
throws ObjectFactoryException {
return getObjectReference(interfaceClass, curSession, Collections.<String, String>emptyMap());
}
@Override
public <T> IPentahoObjectReference<T> getObjectReference( Class<T> interfaceClass, IPentahoSession curSession,
Map<String, String> properties )
throws ObjectFactoryException {
if ( isBundleContextValid() == false ) {
return null;
}
// make sure we check by reference first
if( properties == null || !properties.containsKey(REFERENCE_CLASS) ) {
Map<String, String> props = new HashMap<String, String>();
if (properties != null) {
props.putAll(properties);
}
props.put(REFERENCE_CLASS, interfaceClass.getName());
IPentahoObjectReference<IPentahoObjectReference> objectReference = getObjectReference(IPentahoObjectReference.class, curSession, props);
if( objectReference != null ){
return objectReference.getObject();
}
}
String filter = OSGIUtils.createFilter( properties );
try {
Collection<ServiceReference<T>> refs = context.getServiceReferences( interfaceClass, filter );
if ( refs == null || refs.size() == 0 ) {
log.info( "\n\nOSGI: did not find object: " + interfaceClass.getName() );
return null;
}
ServiceReference[] serviceReferences = refs.toArray( new ServiceReference[ refs.size() ] );
Arrays.sort( serviceReferences, new Comparator<ServiceReference>() {
@Override public int compare( ServiceReference o1, ServiceReference o2 ) {
Object oRank1 = o1.getProperty( Constants.SERVICE_RANKING );
Object oRank2 = o2.getProperty( Constants.SERVICE_RANKING );
Integer rank1 = (oRank1 != null ) ? Integer.valueOf( oRank1.toString() ) : 0;
Integer rank2 = (oRank2 != null ) ? Integer.valueOf( oRank2.toString() ) : 0;
return Integer.compare( rank1 == null ? 0 : rank1, rank2 == null ? 0 : rank2 );
}
} );
ServiceReference<T> serviceReference = serviceReferences[ 0 ];
return new OsgiPentahoObjectReference( this.context, interfaceClass, serviceReference );
} catch ( InvalidSyntaxException e ) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<IPentahoObjectReference<T>> getObjectReferences( Class<T> interfaceClass, IPentahoSession curSession )
throws ObjectFactoryException {
return getObjectReferences( interfaceClass, curSession, Collections.<String, String>emptyMap() );
}
@Override
public <T> List<IPentahoObjectReference<T>> getObjectReferences( Class<T> interfaceClass, IPentahoSession curSession,
Map<String, String> properties )
throws ObjectFactoryException {
if ( isBundleContextValid() == false ) {
return Collections.emptyList();
}
List<IPentahoObjectReference<T>> returnRefs = new ArrayList<IPentahoObjectReference<T>>();
// make sure we check by reference first
if( properties == null || !properties.containsKey(REFERENCE_CLASS) ) {
Map<String, String> props = new HashMap<>();
if (properties != null) {
props.putAll(properties);
}
props.put(REFERENCE_CLASS, interfaceClass.getName());
List<IPentahoObjectReference<IPentahoObjectReference>> objectReferences = getObjectReferences(IPentahoObjectReference.class, curSession, props);
for (IPentahoObjectReference<IPentahoObjectReference> objectReference : objectReferences) {
returnRefs.add(objectReference.getObject());
}
}
String filter = OSGIUtils.createFilter(properties);
try {
Collection<ServiceReference<T>> refs = context.getServiceReferences( interfaceClass, filter );
if ( refs == null || refs.size() == 0 ) {
log.info( "OSGI: did not find object: " + interfaceClass.getName() );
return returnRefs;
}
for ( ServiceReference ref : refs ) {
returnRefs.add( new OsgiPentahoObjectReference<T>( this.context, interfaceClass, ref ) );
}
Collections.sort( returnRefs );
return returnRefs;
} catch ( InvalidSyntaxException e ) {
e.printStackTrace();
}
return Collections.emptyList();
}
@Override
public String getName() {
return getClass().getSimpleName();
}
/**
* Occasionally the Bundle Context will be invalidated before the OSGIObjectFactory wrapping it is de-registered. This
* method checks for this inconsistency and deregisters the OSGIObjectFactory. Callers should handle a false condition
* gracefully, returning null or false to the caller.
*/
private boolean isBundleContextValid() {
try {
// This works in Equinox, but Felix throws an IllegalStateException trying to get the Bundle
int state = context.getBundle().getState();
switch( state ) {
case Bundle.ACTIVE:
return true;
default:
return false;
}
} catch ( IllegalStateException e ) {
return false;
}
}
}