package net.sf.briar.plugins; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.briar.api.ContactId; import net.sf.briar.api.TransportConfig; import net.sf.briar.api.TransportProperties; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.plugins.BatchPlugin; import net.sf.briar.api.plugins.BatchPluginCallback; import net.sf.briar.api.plugins.BatchPluginFactory; import net.sf.briar.api.plugins.Plugin; import net.sf.briar.api.plugins.PluginCallback; import net.sf.briar.api.plugins.PluginExecutor; import net.sf.briar.api.plugins.PluginManager; import net.sf.briar.api.plugins.StreamPlugin; import net.sf.briar.api.plugins.StreamPluginCallback; import net.sf.briar.api.plugins.StreamPluginFactory; import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportIndex; import net.sf.briar.api.transport.BatchTransportReader; import net.sf.briar.api.transport.BatchTransportWriter; import net.sf.briar.api.transport.ConnectionDispatcher; import net.sf.briar.api.transport.StreamTransportConnection; import net.sf.briar.api.ui.UiCallback; import com.google.inject.Inject; class PluginManagerImpl implements PluginManager { private static final Logger LOG = Logger.getLogger(PluginManagerImpl.class.getName()); private static final String[] BATCH_FACTORIES = new String[] { "net.sf.briar.plugins.file.RemovableDrivePluginFactory" }; private static final String[] STREAM_FACTORIES = new String[] { "net.sf.briar.plugins.bluetooth.BluetoothPluginFactory", "net.sf.briar.plugins.socket.SimpleSocketPluginFactory" }; private final ScheduledExecutorService pluginExecutor; private final DatabaseComponent db; private final Poller poller; private final ConnectionDispatcher dispatcher; private final UiCallback uiCallback; private final List batchPlugins; // Locking: this private final List streamPlugins; // Locking: this @Inject PluginManagerImpl(@PluginExecutor ScheduledExecutorService pluginExecutor, DatabaseComponent db, Poller poller, ConnectionDispatcher dispatcher, UiCallback uiCallback) { this.pluginExecutor = pluginExecutor; this.db = db; this.poller = poller; this.dispatcher = dispatcher; this.uiCallback = uiCallback; batchPlugins = new ArrayList(); streamPlugins = new ArrayList(); } public synchronized int getPluginCount() { return batchPlugins.size() + streamPlugins.size(); } public synchronized int startPlugins() { Set ids = new HashSet(); // Instantiate and start the batch plugins for(String s : BATCH_FACTORIES) { try { Class c = Class.forName(s); BatchPluginFactory factory = (BatchPluginFactory) c.newInstance(); BatchCallback callback = new BatchCallback(); BatchPlugin plugin = factory.createPlugin(pluginExecutor, callback); if(plugin == null) { if(LOG.isLoggable(Level.INFO)) { LOG.info(factory.getClass().getSimpleName() + " did not create a plugin"); } continue; } TransportId id = plugin.getId(); if(!ids.add(id)) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Duplicate transport ID: " + id); continue; } TransportIndex index = db.getLocalIndex(id); if(index == null) index = db.addTransport(id); if(index == null) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Could not allocate index for ID: " + id); continue; } callback.init(id, index); plugin.start(); batchPlugins.add(plugin); } catch(ClassCastException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); continue; } catch(Exception e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); continue; } } // Instantiate and start the stream plugins for(String s : STREAM_FACTORIES) { try { Class c = Class.forName(s); StreamPluginFactory factory = (StreamPluginFactory) c.newInstance(); StreamCallback callback = new StreamCallback(); StreamPlugin plugin = factory.createPlugin(pluginExecutor, callback); if(plugin == null) { if(LOG.isLoggable(Level.INFO)) { LOG.info(factory.getClass().getSimpleName() + " did not create a plugin"); } continue; } TransportId id = plugin.getId(); if(!ids.add(id)) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Duplicate transport ID: " + id); continue; } TransportIndex index = db.getLocalIndex(id); if(index == null) index = db.addTransport(id); if(index == null) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Could not allocate index for ID: " + id); continue; } callback.init(id, index); plugin.start(); streamPlugins.add(plugin); } catch(ClassCastException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); continue; } catch(Exception e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); continue; } } // Start the poller List plugins = new ArrayList(); plugins.addAll(batchPlugins); plugins.addAll(streamPlugins); poller.startPolling(Collections.unmodifiableList(plugins)); // Return the number of plugins successfully started return batchPlugins.size() + streamPlugins.size(); } public synchronized int stopPlugins() { int stopped = 0; // Stop the batch plugins for(BatchPlugin plugin : batchPlugins) { try { plugin.stop(); stopped++; } catch(IOException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); } } batchPlugins.clear(); // Stop the stream plugins for(StreamPlugin plugin : streamPlugins) { try { plugin.stop(); stopped++; } catch(IOException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); } } streamPlugins.clear(); // Return the number of plugins successfully stopped return stopped; } private abstract class PluginCallbackImpl implements PluginCallback { protected volatile TransportId id = null; protected volatile TransportIndex index = null; protected void init(TransportId id, TransportIndex index) { assert this.id == null && this.index == null; this.id = id; this.index = index; } public TransportConfig getConfig() { assert id != null; try { return db.getConfig(id); } catch(DbException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); return new TransportConfig(); } } public TransportProperties getLocalProperties() { assert id != null; try { TransportProperties p = db.getLocalProperties(id); return p == null ? new TransportProperties() : p; } catch(DbException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); return new TransportProperties(); } } public Map getRemoteProperties() { assert id != null; try { return db.getRemoteProperties(id); } catch(DbException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); return Collections.emptyMap(); } } public void setConfig(TransportConfig c) { assert id != null; try { db.setConfig(id, c); } catch(DbException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); } } public void setLocalProperties(TransportProperties p) { assert id != null; if(p.size() > ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Plugin " + id + " set too many properties"); return; } for(String s : p.keySet()) { if(s.length() > ProtocolConstants.MAX_PROPERTY_LENGTH) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Plugin " + id + " set long key: " + s); return; } } for(String s : p.values()) { if(s.length() > ProtocolConstants.MAX_PROPERTY_LENGTH) { if(LOG.isLoggable(Level.WARNING)) LOG.warning("Plugin " + id + " set long value: " + s); return; } } try { db.setLocalProperties(id, p); } catch(DbException e) { if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString()); } } public int showChoice(String[] options, String... message) { return uiCallback.showChoice(options, message); } public boolean showConfirmationMessage(String... message) { return uiCallback.showConfirmationMessage(message); } public void showMessage(String... message) { uiCallback.showMessage(message); } } private class BatchCallback extends PluginCallbackImpl implements BatchPluginCallback { public void readerCreated(BatchTransportReader r) { assert id != null; dispatcher.dispatchReader(id, r); } public void writerCreated(ContactId c, BatchTransportWriter w) { assert index != null; dispatcher.dispatchWriter(c, id, index, w); } } private class StreamCallback extends PluginCallbackImpl implements StreamPluginCallback { public void incomingConnectionCreated(StreamTransportConnection s) { assert id != null; dispatcher.dispatchIncomingConnection(id, s); } public void outgoingConnectionCreated(ContactId c, StreamTransportConnection s) { assert index != null; dispatcher.dispatchOutgoingConnection(c, id, index, s); } } }