/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.core.extension; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.logging.LogChannel; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.core.plugins.PluginInterface; import org.pentaho.di.core.plugins.PluginRegistry; import org.pentaho.di.core.plugins.PluginTypeListener; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class maintains a map of ExtensionPointInterface object to its name. */ public class ExtensionPointMap { private static LogChannelInterface log = new LogChannel( "ExtensionPointMap" ); private static ExtensionPointMap INSTANCE = new ExtensionPointMap( PluginRegistry.getInstance() ); private final PluginRegistry registry; private Table<String, String, Supplier<ExtensionPointInterface>> extensionPointPluginMap; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private ExtensionPointMap( PluginRegistry pluginRegistry ) { this.registry = pluginRegistry; extensionPointPluginMap = HashBasedTable.create(); registry.addPluginListener( ExtensionPointPluginType.class, new PluginTypeListener() { @Override public void pluginAdded( Object serviceObject ) { addExtensionPoint( (PluginInterface) serviceObject ); } @Override public void pluginRemoved( Object serviceObject ) { removeExtensionPoint( (PluginInterface) serviceObject ); } @Override public void pluginChanged( Object serviceObject ) { removeExtensionPoint( (PluginInterface) serviceObject ); addExtensionPoint( (PluginInterface) serviceObject ); } } ); List<PluginInterface> extensionPointPlugins = registry.getPlugins( ExtensionPointPluginType.class ); for ( PluginInterface extensionPointPlugin : extensionPointPlugins ) { addExtensionPoint( extensionPointPlugin ); } } public static ExtensionPointMap getInstance() { return INSTANCE; } /** * Add the extension point plugin to the map * * @param extensionPointPlugin */ public void addExtensionPoint( PluginInterface extensionPointPlugin ) { lock.writeLock().lock(); try { for ( String id : extensionPointPlugin.getIds() ) { extensionPointPluginMap.put( extensionPointPlugin.getName(), id, createLazyLoader( extensionPointPlugin ) ); } } finally { lock.writeLock().unlock(); } } /** * Remove the extension point plugin from the map * * @param extensionPointPlugin */ public void removeExtensionPoint( PluginInterface extensionPointPlugin ) { lock.writeLock().lock(); try { for ( String id : extensionPointPlugin.getIds() ) { extensionPointPluginMap.remove( extensionPointPlugin.getName(), id ); } } finally { lock.writeLock().unlock(); } } /** * Reinitialize the extension point plugins map */ public void reInitialize() { lock.writeLock().lock(); try { extensionPointPluginMap = HashBasedTable.create(); final PluginRegistry registry = PluginRegistry.getInstance(); List<PluginInterface> extensionPointPlugins = registry.getPlugins( ExtensionPointPluginType.class ); for ( PluginInterface extensionPointPlugin : extensionPointPlugins ) { addExtensionPoint( extensionPointPlugin ); } } finally { lock.writeLock().unlock(); } } Supplier<ExtensionPointInterface> createLazyLoader( PluginInterface extensionPointPlugin ) { return Suppliers.memoize( new ExtensionPointLoader( extensionPointPlugin ) ); } /** * Call the extension point(s) corresponding to the given id * * This iteration was isolated here to protect against ConcurrentModificationException using PluginRegistry's lock * * @param log log channel to pass to extension point call * @param id the id of the extension point interface * @param object object to pass to extension point call */ public void callExtensionPoint( LogChannelInterface log, String id, Object object ) throws KettleException { lock.readLock().lock(); try { if ( extensionPointPluginMap.containsRow( id ) && !extensionPointPluginMap.rowMap().get( id ).values().isEmpty() ) { for ( Supplier<ExtensionPointInterface> extensionPoint : extensionPointPluginMap.row( id ).values() ) { extensionPoint.get().callExtensionPoint( log, object ); } } } finally { lock.readLock().unlock(); } } /** * Returns the element in the position (rowId,columnId) of the table * * Useful for Unit Testing * * @param rowId the key of the row to be accessed * @param columnId the key of the column to be accessed */ ExtensionPointInterface getTableValue( String rowId, String columnId ) { lock.readLock().lock(); try { return extensionPointPluginMap.contains( rowId, columnId ) ? extensionPointPluginMap.get( rowId, columnId ).get() : null; } finally { lock.readLock().unlock(); } } /** * Returns the number of rows of the table * * Useful for Unit Testing */ int getNumberOfRows() { lock.readLock().lock(); try { return extensionPointPluginMap.rowMap().size(); } finally { lock.readLock().unlock(); } } private class ExtensionPointLoader implements Supplier<ExtensionPointInterface> { private final PluginInterface extensionPointPlugin; private ExtensionPointLoader( PluginInterface extensionPointPlugin ) { this.extensionPointPlugin = extensionPointPlugin; } @Override public ExtensionPointInterface get() { try { return registry.loadClass( extensionPointPlugin, ExtensionPointInterface.class ); } catch ( Exception e ) { getLog().logError( "Unable to load extension point for name = [" + ( extensionPointPlugin != null ? extensionPointPlugin.getName() : "null" ) + "]", e ); return null; } } } public static LogChannelInterface getLog() { if ( log == null ) { log = new LogChannel( "ExtensionPointMap" ); } return log; } }