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