/*
* Copyright 2012 NGDATA nv
*
* 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.lilyproject.plugin.impl;
import javax.annotation.PreDestroy;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lilyproject.plugin.PluginException;
import org.lilyproject.plugin.PluginHandle;
import org.lilyproject.plugin.PluginRegistry;
import org.lilyproject.plugin.PluginUser;
public class PluginRegistryImpl implements PluginRegistry {
private Map<Class, PluginManager> pluginsByType = new HashMap<Class, PluginManager>();
private final Log log = LogFactory.getLog(getClass());
private MBeanServer mbeanServer;
public PluginRegistryImpl(MBeanServer mbeanServer) {
this.mbeanServer = mbeanServer;
}
@Override
public synchronized <T> void addPlugin(Class<T> pluginType, String name, T plugin) {
getManager(pluginType).addPlugin(name, plugin);
}
@Override
public synchronized <T> void removePlugin(Class<T> pluginType, String name, T plugin) {
PluginManager<T> manager = getManager(pluginType);
manager.removePlugin(name, plugin);
if (manager.getPluginCount() == 0 && !manager.isUserSet()) {
manager.destroy();
pluginsByType.remove(pluginType);
}
}
@Override
public synchronized <T> void setPluginUser(Class<T> pluginType, PluginUser<T> pluginUser) {
getManager(pluginType).setPluginUser(pluginUser);
}
@Override
public synchronized <T> void unsetPluginUser(Class<T> pluginType, PluginUser<T> pluginUser) {
getManager(pluginType).unsetPluginUser(pluginUser);
}
@PreDestroy
public synchronized void destroy() {
for (PluginManager manager : pluginsByType.values()) {
if (manager.isUserSet()) {
log.error("Plugin type " + manager.getType().getName() + ": plugin user has not been unset.");
}
if (manager.getPluginCount() > 0) {
StringBuilder pluginNames = new StringBuilder();
for (PluginEntry entry : (List<PluginEntry>)manager.getPlugins()) {
if (pluginNames.length() > 0) {
pluginNames.append(", ");
}
pluginNames.append(entry.getName());
}
log.error("Plugin type " + manager.getType().getName() + ": still " + manager.getPluginCount() + " plugin(s) registered: " + pluginNames);
}
manager.destroy();
}
}
@SuppressWarnings("unchecked")
private <T> PluginManager<T> getManager(Class<T> pluginType) {
PluginManager<T> manager = pluginsByType.get(pluginType);
if (manager == null) {
manager = new PluginManager<T>(pluginType);
pluginsByType.put(pluginType, manager);
}
return manager;
}
public static interface PluginManagerMBean {
String[] getRegisteredNames();
boolean isUserSet();
}
private class PluginManager<T> implements PluginManagerMBean {
private List<PluginEntry<T>> plugins = new ArrayList<PluginEntry<T>>();
private PluginUser<T> user;
private Class<T> type;
private final Log log = LogFactory.getLog(getClass());
private final ObjectName mbeanName;
PluginManager(Class<T> pluginType) {
this.type = pluginType;
String pluginTypeName = pluginType.getName();
int dotPos = pluginTypeName.lastIndexOf('.');
if (dotPos != -1) {
pluginTypeName = pluginTypeName.substring(dotPos + 1);
}
try {
mbeanName = new ObjectName("Lily:name=Plugins,type=" + pluginTypeName);
mbeanServer.registerMBean(this, mbeanName);
} catch (Exception e) {
throw new RuntimeException("Unexpected error registering plugin type as mbean.", e);
}
}
public void addPlugin(String name, T plugin) {
if (name == null || name.trim().length() == 0) {
throw new PluginException("Null, empty or whitespace argument: name");
}
if (plugin == null) {
throw new IllegalArgumentException("Null argument: plugin");
}
if (!type.isAssignableFrom(plugin.getClass())) {
throw new PluginException("Plugin does not implement its plugin type. Plugin \"" + name + "\" of type " + type.getName());
}
PluginEntry<T> newEntry = new PluginEntry<T>(name, plugin);
for (PluginEntry entry : plugins) {
if (entry.equals(newEntry)) {
throw new PluginException("This plugin instance is already registered. Plugin \"" + name + "\" of type " + type.getName());
}
if (entry.name.equals(newEntry.name)) {
throw new PluginException("There is already another plugin registered with this name: \"" + name + "\".");
}
}
plugins.add(newEntry);
notifyPluginAdded(newEntry);
}
public void removePlugin(String name, T plugin) {
PluginEntry<T> removedEntry = new PluginEntry<T>(name, plugin);
boolean found = false;
Iterator<PluginEntry<T>> it = plugins.iterator();
while (it.hasNext()) {
PluginEntry entry = it.next();
if (entry.equals(removedEntry)) {
it.remove();
found = true;
break;
}
}
if (!found) {
throw new PluginException("It is not possible to remove an plugin which is not registered. Plugin \"" + name + "\".");
}
notifyPluginRemoved(removedEntry);
}
public void setPluginUser(PluginUser<T> pluginUser) {
if (this.user != null) {
throw new PluginException("Error setting plugin user: there can be only one PluginUser per plugin type. Type = " + type.getName());
}
this.user = pluginUser;
for (PluginEntry<T> entry : plugins) {
notifyPluginAdded(entry);
}
}
public void unsetPluginUser(PluginUser<T> pluginUser) {
if (this.user != pluginUser) {
throw new PluginException("Error removing plugin user: the current plugin user does not correspond to the specified plugin user.");
}
this.user = null;
}
private void notifyPluginAdded(PluginHandle<T> plugin) {
if (user != null) {
try {
user.pluginAdded(plugin);
} catch (Throwable e) {
log.error("Error notifying plugin user of an added plugin. Plugin \"" + plugin.getName() + "\" of type " + type.getName());
}
}
}
private void notifyPluginRemoved(PluginHandle<T> plugin) {
if (user != null) {
try {
user.pluginRemoved(plugin);
} catch (Throwable e) {
log.error("Error notifying plugin user of a removed plugin. Plugin \"" + plugin.getName() + "\" of type " + type.getName());
}
}
}
@Override
public boolean isUserSet() {
return user != null;
}
public int getPluginCount() {
return plugins.size();
}
public Class getType() {
return type;
}
public List<PluginEntry<T>> getPlugins() {
return plugins;
}
@Override
public String[] getRegisteredNames() {
synchronized (PluginRegistryImpl.this) {
String[] names = new String[plugins.size()];
for (int i = 0; i < plugins.size(); i++) {
names[i] = plugins.get(i).getName();
}
return names;
}
}
public void destroy() {
try {
mbeanServer.unregisterMBean(mbeanName);
} catch (Exception e) {
log.error("Unexpected error unregistering plugin mbean.", e);
}
}
}
private static class PluginEntry<T> implements PluginHandle<T> {
private final String name;
private final T plugin;
PluginEntry(String name, T plugin) {
this.name = name;
this.plugin = plugin;
}
public boolean equals(Object obj) {
if (!(obj instanceof PluginEntry)) {
return false;
}
PluginEntry other = (PluginEntry)obj;
return other.plugin == plugin && other.name.equals(name);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (plugin != null ? plugin.hashCode() : 0);
return result;
}
@Override
public T getPlugin() {
return plugin;
}
@Override
public String getName() {
return name;
}
}
}