001/*
002 * (C) Copyright 2006-2017 Nuxeo (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.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<>();
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                return;
217            }
218            registry.put(name, resource);
219            doBind(resource);
220            if (log.isDebugEnabled()) {
221                log.debug("registered " + name);
222            }
223        }
224
225        protected ObjectName doResolveServiceName(ServiceDescriptor descriptor) {
226            String qualifiedName = descriptor.getName();
227            if (qualifiedName == null) {
228                qualifiedName = ObjectNameFactory.getQualifiedName(descriptor.getResourceClass().getCanonicalName());
229            }
230            return ObjectNameFactory.getObjectName(qualifiedName);
231        }
232
233        protected Resource doResolveServiceDescriptor(ServiceDescriptor descriptor) {
234            Class<?> resourceClass = descriptor.getResourceClass();
235            Object resourceInstance = doResolveService(resourceClass, descriptor);
236            ObjectName managementName = doResolveServiceName(descriptor);
237            Class<?> ifaceClass = descriptor.getIfaceClass();
238            Class<?> managementClass = ifaceClass != null ? ifaceClass : resourceClass;
239            return new Resource(managementName, managementClass, resourceInstance);
240        }
241
242        protected <T> T doResolveService(Class<T> resourceClass, ServiceDescriptor descriptor) {
243            T service = Framework.getService(resourceClass);
244            if (service == null) {
245                throw new ManagementRuntimeException("Cannot locate resource using " + resourceClass);
246            }
247            return service;
248        }
249
250        protected void doUnregisterResources() {
251            Iterator<Entry<ObjectName, Resource>> iterator = registry.entrySet().iterator();
252            while (iterator.hasNext()) {
253                Entry<ObjectName, Resource> entry = iterator.next();
254                iterator.remove();
255                Resource resource = entry.getValue();
256                if (resource.mbean != null) {
257                    doUnbind(entry.getValue());
258                }
259            }
260        }
261
262        protected void doUnregisterResource(ServiceDescriptor descriptor) {
263            ObjectName objectName = doResolveServiceName(descriptor);
264            doUnregisterResource(objectName);
265            String shortName = descriptor.getName();
266            if (!StringUtils.isEmpty(shortName)) {
267                shortcutsRegistry.unregisterShortcut(shortName);
268            }
269        }
270
271        protected void doUnregisterResource(String qualifiedName) {
272            ObjectName objectName = ObjectNameFactory.getObjectName(qualifiedName);
273            doUnregisterResource(objectName);
274        }
275
276        protected void doUnregisterResource(ObjectName objectName) {
277            Resource resource = registry.remove(objectName);
278            if (resource == null) {
279                throw new IllegalArgumentException(objectName + " is not registered");
280            }
281            if (resource.mbean != null) {
282                doUnbind(resource);
283            }
284        }
285
286    }
287
288    @Override
289    public void registerResource(String shortName, String qualifiedName, Class<?> managementClass, Object instance) {
290        resourcesRegistry.doRegisterResource(qualifiedName, managementClass, instance);
291        if (shortName != null) {
292            shortcutsRegistry.doRegisterShortcut(shortName, qualifiedName);
293        }
294    }
295
296    @Override
297    public void unregisterResource(String shortName, String qualifiedName) {
298        resourcesRegistry.doUnregisterResource(qualifiedName);
299        if (shortName != null) {
300            shortcutsRegistry.doUnregisterShortcut(shortName);
301        }
302    }
303
304    public void registerShortcut(String shortName, String qualifiedName) {
305        shortcutsRegistry.doRegisterShortcut(shortName, qualifiedName);
306    }
307
308    public void unregisterShortcut(String shortName) {
309        shortcutsRegistry.doUnregisterShortcut(shortName);
310    }
311
312    @Override
313    public Set<String> getShortcutsName() {
314        return new HashSet<>(shortcutsRegistry.registry.keySet());
315    }
316
317    @Override
318    public Set<ObjectName> getResourcesName() {
319        return new HashSet<>(resourcesRegistry.registry.keySet());
320    }
321
322    @Override
323    public ObjectName lookupName(String name) {
324        if (!shortcutsRegistry.registry.containsKey(name)) {
325            return ObjectNameFactory.getObjectName(name);
326        }
327        return shortcutsRegistry.registry.get(name);
328    }
329
330    protected void doBindResources() {
331        for (Resource resource : resourcesRegistry.registry.values()) {
332            if (resource.mbean == null) {
333                resourcesRegistry.doBind(resource);
334            }
335        }
336    }
337
338    @Override
339    public void bindResources() {
340        doBindResources();
341    }
342
343    protected void doUnbindResources() {
344        for (Resource resource : resourcesRegistry.registry.values()) {
345            if (resource.mbean != null) {
346                resourcesRegistry.doUnbind(resource);
347            }
348        }
349    }
350
351    @Override
352    public void unbindResources() {
353        doUnbindResources();
354    }
355
356    protected boolean started = false;
357
358    @Override
359    public void start(ComponentContext context) {
360        started = true;
361        factoriesRegistry.doRegisterResources();
362        doBindResources();
363    }
364
365    @Override
366    public void stop(ComponentContext context) {
367        started = false;
368        doUnbindResources();
369    }
370
371    @Override
372    public void activate(ComponentContext context) {
373        serverLocatorService = (ServerLocatorService) Framework.getLocalService(ServerLocator.class);
374    }
375
376    @Override
377    public void deactivate(ComponentContext context) {
378        resourcesRegistry.doUnregisterResources();
379    }
380
381    public void bindResource(ObjectName name) {
382        Resource resource = resourcesRegistry.registry.get(name);
383        if (resource == null) {
384            throw new IllegalArgumentException(name + " is not registered");
385        }
386        resourcesRegistry.doBind(resource);
387    }
388
389    public void unbindResource(ObjectName name) {
390        Resource resource = resourcesRegistry.registry.get(name);
391        if (resource == null) {
392            throw new IllegalArgumentException(name + " is not registered");
393        }
394        resourcesRegistry.doUnbind(resource);
395    }
396
397    protected void bindForTest(MBeanServer server, ObjectName name, Object instance, Class<?> clazz)
398            throws JMException, InvalidTargetObjectTypeException {
399        resourcesRegistry.doBind(server, name, instance, clazz);
400    }
401
402}