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