001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *      Stephane Lacoin (Nuxeo EP Software Engineer)
016 */
017package org.nuxeo.runtime.management;
018
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025import java.util.TreeMap;
026
027import javax.management.JMException;
028import javax.management.MBeanServer;
029import javax.management.ObjectName;
030import javax.management.modelmbean.InvalidTargetObjectTypeException;
031import javax.management.modelmbean.RequiredModelMBean;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.runtime.RuntimeServiceEvent;
037import org.nuxeo.runtime.RuntimeServiceListener;
038import org.nuxeo.runtime.api.Framework;
039import org.nuxeo.runtime.management.inspector.ModelMBeanInfoFactory;
040import org.nuxeo.runtime.model.ComponentContext;
041import org.nuxeo.runtime.model.ComponentInstance;
042import org.nuxeo.runtime.model.ComponentName;
043import org.nuxeo.runtime.model.DefaultComponent;
044
045/**
046 * @author Stephane Lacoin (Nuxeo EP Software Engineer)
047 */
048public class ResourcePublisherService extends DefaultComponent implements ResourcePublisher, ResourcePublisherMBean {
049
050    public static final String SERVICES_EXT_KEY = "services";
051
052    public static final String FACTORIES_EXT_KEY = "factories";
053
054    public static final String SHORTCUTS_EXT_KEY = "shortcuts";
055
056    public static final ComponentName NAME = new ComponentName("org.nuxeo.runtime.management.ResourcePublisher");
057
058    private static final Log log = LogFactory.getLog(ResourcePublisherService.class);
059
060    protected final ShortcutsRegistry shortcutsRegistry = new ShortcutsRegistry();
061
062    protected final FactoriesRegistry factoriesRegistry = new FactoriesRegistry();
063
064    protected final ResourcesRegistry resourcesRegistry = new ResourcesRegistry();
065
066    protected ServerLocatorService serverLocatorService;
067
068    public ResourcePublisherService() {
069        super(); // enables breaking
070    }
071
072    @Override
073    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
074        if (extensionPoint.equals(SERVICES_EXT_KEY)) {
075            resourcesRegistry.doRegisterResource((ServiceDescriptor) contribution);
076        } else if (extensionPoint.equals(FACTORIES_EXT_KEY)) {
077            factoriesRegistry.doRegisterFactory((ResourceFactoryDescriptor) contribution);
078        } else if (extensionPoint.equals(SHORTCUTS_EXT_KEY)) {
079            shortcutsRegistry.doRegisterShortcut((ShortcutDescriptor) contribution);
080        }
081    }
082
083    @Override
084    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
085        if (extensionPoint.equals(SERVICES_EXT_KEY)) {
086            resourcesRegistry.doUnregisterResource((ServiceDescriptor) contribution);
087        } else if (extensionPoint.equals(FACTORIES_EXT_KEY)) {
088            factoriesRegistry.doUnregisterFactory((ResourceFactoryDescriptor) contribution);
089        } else if (extensionPoint.equals(SHORTCUTS_EXT_KEY)) {
090            shortcutsRegistry.doUnregisterShortcut((ShortcutDescriptor) contribution);
091        }
092    }
093
094    protected class FactoriesRegistry {
095
096        protected final Map<Class<? extends ResourceFactory>, ResourceFactory> registry = new HashMap<Class<? extends ResourceFactory>, ResourceFactory>();
097
098        protected void doRegisterFactory(ResourceFactoryDescriptor descriptor) {
099            ResourceFactory factory;
100            Class<? extends ResourceFactory> factoryClass = descriptor.getFactoryClass();
101            try {
102                factory = factoryClass.newInstance();
103            } catch (ReflectiveOperationException e) {
104                throw new ManagementRuntimeException("Cannot create factory " + factoryClass, e);
105            }
106            factory.configure(ResourcePublisherService.this, descriptor);
107            registry.put(factoryClass, factory);
108        }
109
110        protected void doUnregisterFactory(ResourceFactoryDescriptor descriptor) {
111            registry.remove(descriptor.getFactoryClass());
112        }
113
114        protected void doRegisterResources() {
115            for (ResourceFactory factory : registry.values()) {
116                factory.registerResources();
117            }
118        }
119    }
120
121    protected class ShortcutsRegistry {
122        protected final Map<String, ObjectName> registry = new TreeMap<String, ObjectName>();
123
124        protected void doRegisterShortcut(ShortcutDescriptor descriptor) {
125            doRegisterShortcut(descriptor.getShortName(), descriptor.getQualifiedName());
126        }
127
128        protected void doRegisterShortcut(String shortName, String qualifiedName) {
129            registry.put(shortName, ObjectNameFactory.getObjectName(qualifiedName));
130        }
131
132        protected void doRegisterShortcut(String shortName, ObjectName qualifiedName) {
133            registry.put(shortName, qualifiedName);
134        }
135
136        protected void doUnregisterShortcut(ShortcutDescriptor descriptor) {
137            doUnregisterShortcut(descriptor.getShortName());
138        }
139
140        protected void doUnregisterShortcut(String name) {
141            registry.remove(name);
142        }
143
144        public void unregisterShortcut(String name) {
145            doUnregisterShortcut(name);
146        }
147    }
148
149    protected class ResourcesRegistry {
150
151        protected final Map<ObjectName, Resource> registry = new HashMap<ObjectName, Resource>();
152
153        protected void doRegisterResource(String qualifiedName, Class<?> info, Object instance) {
154            Resource resource = new Resource(ObjectNameFactory.getObjectName(qualifiedName), info, instance);
155            doRegisterResource(resource);
156        }
157
158        protected void doRegisterResource(ServiceDescriptor descriptor) {
159            Resource resource = doResolveServiceDescriptor(descriptor);
160            doRegisterResource(resource);
161            String shortName = descriptor.getName();
162            if (!StringUtils.isEmpty(shortName)) {
163                shortcutsRegistry.doRegisterShortcut(shortName, resource.getManagementName());
164            }
165        }
166
167        protected final ModelMBeanInfoFactory mbeanInfoFactory = new ModelMBeanInfoFactory();
168
169        protected RequiredModelMBean doBind(MBeanServer server, ObjectName name, Object instance, Class<?> clazz)
170                throws JMException, InvalidTargetObjectTypeException {
171            RequiredModelMBean mbean = new RequiredModelMBean();
172            mbean.setManagedResource(instance, "ObjectReference");
173            mbean.setModelMBeanInfo(mbeanInfoFactory.getModelMBeanInfo(clazz));
174            server.registerMBean(mbean, name);
175            return mbean;
176        }
177
178        protected void doBind(Resource resource) {
179            if (!started) {
180                return;
181            }
182            if (resource.mbean != null) {
183                throw new IllegalStateException(resource + " is already bound");
184            }
185            MBeanServer server = serverLocatorService.lookupServer(resource.managementName.getDomain());
186            try {
187                resource.mbean = doBind(server, resource.managementName, resource.instance, resource.clazz);
188                if (ResourcePublisherService.log.isDebugEnabled()) {
189                    ResourcePublisherService.log.debug("bound " + resource);
190                }
191            } catch (JMException | InvalidTargetObjectTypeException e) {
192                ResourcePublisherService.log.error("Cannot bind " + resource, e);
193            }
194        }
195
196        protected void doUnbind(Resource resource) {
197            if (resource.mbean == null) {
198                throw new IllegalStateException(resource.managementName + " is not bound");
199            }
200            try {
201                MBeanServer server = serverLocatorService.lookupServer(resource.managementName);
202                server.unregisterMBean(resource.managementName);
203            } catch (JMException e) {
204                throw ManagementRuntimeException.wrap("Cannot unbind " + resource, e);
205            } finally {
206                resource.mbean = null;
207                if (ResourcePublisherService.log.isDebugEnabled()) {
208                    ResourcePublisherService.log.debug("unbound " + resource);
209                }
210            }
211        }
212
213        protected void doRegisterResource(Resource resource) {
214            final ObjectName name = resource.getManagementName();
215            if (registry.containsKey(name)) {
216                log.warn("Already registered " + name + ", skipping", new Throwable("Stack trace"));
217                return;
218            }
219            registry.put(name, resource);
220            doBind(resource);
221            if (log.isDebugEnabled()) {
222                log.debug("registered " + name);
223            }
224        }
225
226        protected ObjectName doResolveServiceName(ServiceDescriptor descriptor) {
227            String qualifiedName = descriptor.getName();
228            if (qualifiedName == null) {
229                qualifiedName = ObjectNameFactory.getQualifiedName(descriptor.getResourceClass().getCanonicalName());
230            }
231            return ObjectNameFactory.getObjectName(qualifiedName);
232        }
233
234        protected Resource doResolveServiceDescriptor(ServiceDescriptor descriptor) {
235            Class<?> resourceClass = descriptor.getResourceClass();
236            Object resourceInstance = doResolveService(resourceClass, descriptor);
237            ObjectName managementName = doResolveServiceName(descriptor);
238            Class<?> ifaceClass = descriptor.getIfaceClass();
239            Class<?> managementClass = ifaceClass != null ? ifaceClass : resourceClass;
240            return new Resource(managementName, managementClass, resourceInstance);
241        }
242
243        protected <T> T doResolveService(Class<T> resourceClass, ServiceDescriptor descriptor) {
244            T service = Framework.getService(resourceClass);
245            if (service == null) {
246                throw new ManagementRuntimeException("Cannot locate resource using " + resourceClass);
247            }
248            return service;
249        }
250
251        protected void doUnregisterResources() {
252            Iterator<Entry<ObjectName, Resource>> iterator = registry.entrySet().iterator();
253            while (iterator.hasNext()) {
254                Entry<ObjectName, Resource> entry = iterator.next();
255                iterator.remove();
256                Resource resource = entry.getValue();
257                if (resource.mbean != null) {
258                    doUnbind(entry.getValue());
259                }
260            }
261        }
262
263        protected void doUnregisterResource(ServiceDescriptor descriptor) {
264            ObjectName objectName = doResolveServiceName(descriptor);
265            doUnregisterResource(objectName);
266            String shortName = descriptor.getName();
267            if (!StringUtils.isEmpty(shortName)) {
268                shortcutsRegistry.unregisterShortcut(shortName);
269            }
270        }
271
272        protected void doUnregisterResource(String qualifiedName) {
273            ObjectName objectName = ObjectNameFactory.getObjectName(qualifiedName);
274            doUnregisterResource(objectName);
275        }
276
277        protected void doUnregisterResource(ObjectName objectName) {
278            Resource resource = registry.remove(objectName);
279            if (resource == null) {
280                throw new IllegalArgumentException(objectName + " is not registered");
281            }
282            if (resource.mbean != null) {
283                doUnbind(resource);
284            }
285        }
286
287    }
288
289    @Override
290    public void registerResource(String shortName, String qualifiedName, Class<?> managementClass, Object instance) {
291        resourcesRegistry.doRegisterResource(qualifiedName, managementClass, instance);
292        if (shortName != null) {
293            shortcutsRegistry.doRegisterShortcut(shortName, qualifiedName);
294        }
295    }
296
297    @Override
298    public void unregisterResource(String shortName, String qualifiedName) {
299        resourcesRegistry.doUnregisterResource(qualifiedName);
300        if (shortName != null) {
301            shortcutsRegistry.doUnregisterShortcut(shortName);
302        }
303    }
304
305    public void registerShortcut(String shortName, String qualifiedName) {
306        shortcutsRegistry.doRegisterShortcut(shortName, qualifiedName);
307    }
308
309    public void unregisterShortcut(String shortName) {
310        shortcutsRegistry.doUnregisterShortcut(shortName);
311    }
312
313    @Override
314    public Set<String> getShortcutsName() {
315        return new HashSet<String>(shortcutsRegistry.registry.keySet());
316    }
317
318    @Override
319    public Set<ObjectName> getResourcesName() {
320        return new HashSet<ObjectName>(resourcesRegistry.registry.keySet());
321    }
322
323    @Override
324    public ObjectName lookupName(String name) {
325        if (!shortcutsRegistry.registry.containsKey(name)) {
326            return ObjectNameFactory.getObjectName(name);
327        }
328        return shortcutsRegistry.registry.get(name);
329    }
330
331    protected void doBindResources() {
332        for (Resource resource : resourcesRegistry.registry.values()) {
333            if (resource.mbean == null) {
334                resourcesRegistry.doBind(resource);
335            }
336        }
337    }
338
339    @Override
340    public void bindResources() {
341        doBindResources();
342    }
343
344    protected void doUnbindResources() {
345        for (Resource resource : resourcesRegistry.registry.values()) {
346            if (resource.mbean != null) {
347                resourcesRegistry.doUnbind(resource);
348            } else {;
349            }
350        }
351    }
352
353    @Override
354    public void unbindResources() {
355        doUnbindResources();
356    }
357
358    protected boolean started = false;
359
360    @Override
361    public void applicationStarted(ComponentContext context) {
362        started = true;
363        factoriesRegistry.doRegisterResources();
364        doBindResources();
365        Framework.addListener(new RuntimeServiceListener() {
366
367            @Override
368            public void handleEvent(RuntimeServiceEvent event) {
369                if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) {
370                    return;
371                }
372                Framework.removeListener(this);
373                doUnbindResources();
374            }
375        });
376    }
377
378    @Override
379    public void activate(ComponentContext context) {
380        serverLocatorService = (ServerLocatorService) Framework.getLocalService(ServerLocator.class);
381    }
382
383    @Override
384    public void deactivate(ComponentContext context) {
385        resourcesRegistry.doUnregisterResources();
386    }
387
388    public void bindResource(ObjectName name) {
389        Resource resource = resourcesRegistry.registry.get(name);
390        if (resource == null) {
391            throw new IllegalArgumentException(name + " is not registered");
392        }
393        resourcesRegistry.doBind(resource);
394    }
395
396    public void unbindResource(ObjectName name) {
397        Resource resource = resourcesRegistry.registry.get(name);
398        if (resource == null) {
399            throw new IllegalArgumentException(name + " is not registered");
400        }
401        resourcesRegistry.doUnbind(resource);
402    }
403
404    protected void bindForTest(MBeanServer server, ObjectName name, Object instance, Class<?> clazz)
405            throws JMException, InvalidTargetObjectTypeException {
406        resourcesRegistry.doBind(server, name, instance, clazz);
407    }
408
409}