| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.felix.webconsole.plugins.ds.internal; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.felix.utils.json.JSONWriter; |
| import org.apache.felix.webconsole.DefaultVariableResolver; |
| import org.apache.felix.webconsole.SimpleWebConsolePlugin; |
| import org.apache.felix.webconsole.WebConsoleUtil; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.service.component.ComponentConstants; |
| import org.osgi.service.component.runtime.ServiceComponentRuntime; |
| import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO; |
| import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO; |
| import org.osgi.service.component.runtime.dto.ReferenceDTO; |
| import org.osgi.service.component.runtime.dto.SatisfiedReferenceDTO; |
| import org.osgi.util.promise.Promise; |
| |
| /** |
| * ComponentsServlet provides a plugin for managing Service Components Runtime. |
| */ |
| class WebConsolePlugin extends SimpleWebConsolePlugin |
| { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final String LABEL = "components"; //$NON-NLS-1$ |
| private static final String TITLE = "%components.pluginTitle"; //$NON-NLS-1$ |
| private static final String CATEGORY = "OSGi"; //$NON-NLS-1$ |
| private static final String CSS[] = { "/res/ui/bundles.css" }; // yes, it's correct! //$NON-NLS-1$ |
| private static final String RES = "/" + LABEL + "/res/"; //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| // actions |
| private static final String OPERATION = "action"; //$NON-NLS-1$ |
| private static final String OPERATION_ENABLE = "enable"; //$NON-NLS-1$ |
| private static final String OPERATION_DISABLE = "disable"; //$NON-NLS-1$ |
| //private static final String OPERATION_CONFIGURE = "configure"; |
| |
| // templates |
| private final String TEMPLATE; |
| |
| private volatile ConfigurationSupport optionalSupport; |
| |
| private final ServiceComponentRuntime runtime; |
| |
| /** Default constructor */ |
| WebConsolePlugin(final ServiceComponentRuntime service) |
| { |
| super(LABEL, TITLE, CSS); |
| |
| this.runtime = service; |
| // load templates |
| TEMPLATE = readTemplateFile("/res/plugin.html"); //$NON-NLS-1$ |
| } |
| |
| |
| @Override |
| public void deactivate() { |
| if ( this.optionalSupport != null ) |
| { |
| this.optionalSupport.close(); |
| this.optionalSupport = null; |
| } |
| super.deactivate(); |
| } |
| |
| |
| @Override |
| public void activate(final BundleContext bundleContext) |
| { |
| super.activate(bundleContext); |
| this.optionalSupport = new ConfigurationSupport(bundleContext); |
| } |
| |
| |
| @Override |
| public String getCategory() |
| { |
| return CATEGORY; |
| } |
| |
| private void wait(final Promise<Void> p ) |
| { |
| while ( !p.isDone() ) |
| { |
| try |
| { |
| Thread.sleep(5); |
| } |
| catch (final InterruptedException e) |
| { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| |
| /** |
| * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| @Override |
| protected void doPost(HttpServletRequest request, HttpServletResponse response) |
| throws IOException |
| { |
| final String op = request.getParameter(OPERATION); |
| RequestInfo reqInfo = new RequestInfo(request, true); |
| if (reqInfo.componentRequested) |
| { |
| boolean found = false; |
| if ( reqInfo.component != null ) |
| { |
| if (OPERATION_ENABLE.equals(op)) |
| { |
| wait(this.runtime.enableComponent(reqInfo.component.description)); |
| reqInfo = new RequestInfo(request, false); |
| found = true; |
| } |
| else if ( OPERATION_DISABLE.equals(op) ) |
| { |
| wait(this.runtime.disableComponent(reqInfo.component.description)); |
| found = true; |
| } |
| } |
| if ( !found ) |
| { |
| response.sendError(404); |
| return; |
| } |
| } |
| else |
| { |
| response.sendError(500); |
| return; |
| } |
| |
| final PrintWriter pw = response.getWriter(); |
| response.setContentType("application/json"); //$NON-NLS-1$ |
| response.setCharacterEncoding("UTF-8"); //$NON-NLS-1$ |
| renderResult(pw, reqInfo, null); |
| } |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| @Override |
| protected void doGet(HttpServletRequest request, HttpServletResponse response) |
| throws ServletException, IOException |
| { |
| String path = request.getPathInfo(); |
| // don't process if this is request to load a resource |
| if (!path.startsWith(RES)) |
| { |
| final RequestInfo reqInfo = new RequestInfo(request, true); |
| if (reqInfo.component == null && reqInfo.componentRequested) |
| { |
| response.sendError(404); |
| return; |
| } |
| if (reqInfo.extension.equals("json")) //$NON-NLS-1$ |
| { |
| response.setContentType("application/json"); //$NON-NLS-1$ |
| response.setCharacterEncoding("UTF-8"); //$NON-NLS-1$ |
| |
| this.renderResult(response.getWriter(), reqInfo, reqInfo.component); |
| |
| // nothing more to do |
| return; |
| } |
| } |
| super.doGet(request, response); |
| } |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| protected void renderContent(HttpServletRequest request, HttpServletResponse response) |
| throws IOException |
| { |
| // get request info from request attribute |
| final RequestInfo reqInfo = getRequestInfo(request); |
| |
| StringWriter w = new StringWriter(); |
| PrintWriter w2 = new PrintWriter(w); |
| renderResult(w2, reqInfo, reqInfo.component); |
| |
| // prepare variables |
| DefaultVariableResolver vars = ((DefaultVariableResolver) WebConsoleUtil.getVariableResolver(request)); |
| vars.put("__drawDetails__", reqInfo.componentRequested ? Boolean.TRUE : Boolean.FALSE); //$NON-NLS-1$ |
| vars.put("__data__", w.toString()); //$NON-NLS-1$ |
| |
| response.getWriter().print(TEMPLATE); |
| |
| } |
| |
| private void renderResult(final PrintWriter pw, RequestInfo info, final ComponentConfigurationDTO component) |
| throws IOException |
| { |
| final JSONWriter jw = new JSONWriter(pw); |
| jw.object(); |
| |
| jw.key("status"); //$NON-NLS-1$ |
| jw.value(info.configurations.size()); |
| if ( !info.configurations.isEmpty() ) |
| { |
| // render components |
| jw.key("data"); //$NON-NLS-1$ |
| jw.array(); |
| if (component != null) |
| { |
| if ( component.state == -1 ) |
| { |
| component(jw, component.description, null, true); |
| } |
| else |
| { |
| component(jw, component.description, component, true); |
| } |
| } |
| else |
| { |
| for( final ComponentDescriptionDTO cd : info.disabled ) |
| { |
| component(jw, cd, null, false); |
| } |
| for (final ComponentConfigurationDTO cfg : info.configurations) |
| { |
| component(jw, cfg.description, cfg, false); |
| } |
| } |
| jw.endArray(); |
| } |
| |
| jw.endObject(); |
| } |
| |
| void writePid(final JSONWriter jw, final ComponentDescriptionDTO desc) throws IOException |
| { |
| final String configurationPid = desc.configurationPid[0]; |
| final String pid; |
| if (desc.configurationPid.length == 1) { |
| pid = configurationPid; |
| } else { |
| pid = Arrays.toString(desc.configurationPid); |
| } |
| jw.key("pid"); //$NON-NLS-1$ |
| jw.value(pid); |
| if (this.optionalSupport.isConfigurable( |
| this.getBundleContext().getBundle(0).getBundleContext().getBundle(desc.bundle.id), |
| configurationPid)) |
| { |
| jw.key("configurable"); //$NON-NLS-1$ |
| jw.value(configurationPid); |
| } |
| } |
| |
| void component(JSONWriter jw, |
| final ComponentDescriptionDTO desc, |
| final ComponentConfigurationDTO config, boolean details) throws IOException |
| { |
| String id = config == null ? "" : String.valueOf(config.id); |
| String name = desc.name; |
| |
| jw.object(); |
| |
| // component information |
| jw.key("id"); //$NON-NLS-1$ |
| jw.value(id); |
| jw.key("bundleId"); //$NON-NLS-1$ |
| jw.value(desc.bundle.id); |
| jw.key("name"); //$NON-NLS-1$ |
| jw.value(name); |
| jw.key("state"); //$NON-NLS-1$ |
| if ( config != null ) |
| { |
| jw.value(ComponentConfigurationPrinter.toStateString(config.state)); |
| jw.key("stateRaw"); //$NON-NLS-1$ |
| jw.value(config.state); |
| } |
| else |
| { |
| if ( desc.defaultEnabled && "require".equals(desc.configurationPolicy)) |
| { |
| jw.value("no config"); |
| } |
| else |
| { |
| jw.value("disabled"); //$NON-NLS-1$ |
| } |
| jw.key("stateRaw"); //$NON-NLS-1$ |
| jw.value(-1); |
| } |
| |
| writePid(jw, desc); |
| |
| // component details |
| if (details) |
| { |
| gatherComponentDetails(jw, desc, config); |
| } |
| |
| jw.endObject(); |
| } |
| |
| private void gatherComponentDetails(JSONWriter jw, |
| ComponentDescriptionDTO desc, |
| ComponentConfigurationDTO component) throws IOException |
| { |
| final Bundle bundle = this.getBundleContext().getBundle(0).getBundleContext().getBundle(desc.bundle.id); |
| |
| jw.key("props"); //$NON-NLS-1$ |
| jw.array(); |
| |
| keyVal(jw, "Bundle", bundle.getSymbolicName() + " (" |
| + bundle.getBundleId() + ")"); |
| keyVal(jw, "Implementation Class", desc.implementationClass); |
| if (desc.factory != null) |
| { |
| keyVal(jw, "Component Factory Name", desc.factory); |
| } |
| keyVal(jw, "Default State", desc.defaultEnabled ? "enabled" : "disabled"); |
| keyVal(jw, "Activation", desc.immediate ? "immediate" : "delayed"); |
| |
| keyVal(jw, "Configuration Policy", desc.configurationPolicy); |
| |
| if ( component != null && component.state == ComponentConfigurationDTO.FAILED_ACTIVATION && component.failure != null ) { |
| keyVal(jw, "failure", component.failure); |
| } |
| if ( component != null && component.service != null ) { |
| keyVal(jw, "serviceId", component.service.id); |
| } |
| listServices(jw, desc); |
| if (desc.configurationPid.length == 1) { |
| keyVal(jw, "PID", desc.configurationPid[0]); |
| } else { |
| keyVal(jw, "PIDs", Arrays.toString(desc.configurationPid)); |
| } |
| listReferences(jw, desc, component); |
| listProperties(jw, desc, component); |
| |
| jw.endArray(); |
| } |
| |
| private void listServices(JSONWriter jw, ComponentDescriptionDTO desc) throws IOException |
| { |
| String[] services = desc.serviceInterfaces; |
| if (services == null) |
| { |
| return; |
| } |
| |
| if ( desc.scope != null ) { |
| keyVal(jw, "Service Type", desc.scope); |
| } |
| jw.object(); |
| jw.key("key"); |
| jw.value("Services"); |
| jw.key("value"); |
| jw.array(); |
| for (int i = 0; i < services.length; i++) |
| { |
| jw.value(services[i]); |
| } |
| jw.endArray(); |
| jw.endObject(); |
| } |
| |
| private SatisfiedReferenceDTO findReference(final ComponentConfigurationDTO component, final String name) |
| { |
| for(final SatisfiedReferenceDTO dto : component.satisfiedReferences) |
| { |
| if ( dto.name.equals(name)) |
| { |
| return dto; |
| } |
| } |
| return null; |
| } |
| |
| private void listReferences(JSONWriter jw, ComponentDescriptionDTO desc, ComponentConfigurationDTO config) throws IOException |
| { |
| for(final ReferenceDTO dto : desc.references) |
| { |
| jw.object(); |
| jw.key("key"); |
| jw.value("Reference " + dto.name); |
| jw.key("value"); |
| jw.array(); |
| final SatisfiedReferenceDTO satisfiedRef; |
| if ( config != null ) |
| { |
| satisfiedRef = findReference(config, dto.name); |
| |
| jw.value(satisfiedRef != null ? "Satisfied" : "Unsatisfied"); |
| } |
| else |
| { |
| satisfiedRef = null; |
| } |
| jw.value("Service Name: " + dto.interfaceName); |
| if (dto.target != null) |
| { |
| jw.value("Target Filter: " + dto.target); |
| } |
| jw.value("Cardinality: " + dto.cardinality); |
| jw.value("Policy: " + dto.policy); |
| jw.value("Policy Option: " + dto.policyOption); |
| |
| // list bound services |
| if ( satisfiedRef != null ) |
| { |
| for (int j = 0; j < satisfiedRef.boundServices.length; j++) |
| { |
| final StringBuffer b = new StringBuffer(); |
| b.append("Bound Service ID "); |
| b.append(satisfiedRef.boundServices[j].id); |
| |
| String name = (String) satisfiedRef.boundServices[j].properties.get(ComponentConstants.COMPONENT_NAME); |
| if (name == null) |
| { |
| name = (String) satisfiedRef.boundServices[j].properties.get(Constants.SERVICE_PID); |
| if (name == null) |
| { |
| name = (String) satisfiedRef.boundServices[j].properties.get(Constants.SERVICE_DESCRIPTION); |
| } |
| } |
| if (name != null) |
| { |
| b.append(" ("); |
| b.append(name); |
| b.append(")"); |
| } |
| jw.value(b.toString()); |
| } |
| } |
| else if ( config != null ) |
| { |
| jw.value("No Services bound"); |
| } |
| |
| jw.endArray(); |
| jw.endObject(); |
| } |
| } |
| |
| private void listProperties(JSONWriter jw, ComponentDescriptionDTO desc, ComponentConfigurationDTO component) throws IOException |
| { |
| Map<String, Object> props = component != null ? component.properties : desc.properties; |
| if (props != null) |
| { |
| jw.object(); |
| jw.key("key"); |
| jw.value("Properties"); |
| jw.key("value"); |
| jw.array(); |
| TreeSet<String> keys = new TreeSet<String>(props.keySet()); |
| for (Iterator<String> ki = keys.iterator(); ki.hasNext();) |
| { |
| final String key = ki.next(); |
| final StringBuilder b = new StringBuilder(); |
| b.append(key).append(" = "); |
| |
| Object prop = props.get(key); |
| prop = WebConsoleUtil.toString(prop); |
| b.append(prop); |
| jw.value(b.toString()); |
| } |
| jw.endArray(); |
| jw.endObject(); |
| } |
| if ( component == null && desc.factoryProperties != null ) { |
| jw.object(); |
| jw.key("key"); |
| jw.value("FactoryProperties"); |
| jw.key("value"); |
| jw.array(); |
| TreeSet<String> keys = new TreeSet<String>(desc.factoryProperties.keySet()); |
| for (Iterator<String> ki = keys.iterator(); ki.hasNext();) |
| { |
| final String key = ki.next(); |
| final StringBuilder b = new StringBuilder(); |
| b.append(key).append(" = "); |
| |
| Object prop = props.get(key); |
| prop = WebConsoleUtil.toString(prop); |
| b.append(prop); |
| jw.value(b.toString()); |
| } |
| jw.endArray(); |
| jw.endObject(); |
| } |
| } |
| |
| private void keyVal(JSONWriter jw, String key, Object value) throws IOException |
| { |
| if (key != null && value != null) |
| { |
| jw.object(); |
| jw.key("key"); //$NON-NLS-1$ |
| jw.value(key); |
| jw.key("value"); //$NON-NLS-1$ |
| jw.value(value); |
| jw.endObject(); |
| } |
| } |
| |
| |
| |
| private final class RequestInfo |
| { |
| public final String extension; |
| public final ComponentConfigurationDTO component; |
| public final boolean componentRequested; |
| public final List<ComponentDescriptionDTO> descriptions = new ArrayList<ComponentDescriptionDTO>(); |
| public final List<ComponentConfigurationDTO> configurations = new ArrayList<ComponentConfigurationDTO>(); |
| public final List<ComponentDescriptionDTO> disabled = new ArrayList<ComponentDescriptionDTO>(); |
| |
| protected RequestInfo(final HttpServletRequest request, final boolean checkPathInfo) |
| { |
| String info = request.getPathInfo(); |
| // remove label and starting slash |
| info = info.substring(getLabel().length() + 1); |
| |
| // get extension |
| if (info.endsWith(".json")) //$NON-NLS-1$ |
| { |
| extension = "json"; //$NON-NLS-1$ |
| info = info.substring(0, info.length() - 5); |
| } |
| else |
| { |
| extension = "html"; //$NON-NLS-1$ |
| } |
| |
| this.descriptions.addAll(runtime.getComponentDescriptionDTOs()); |
| if (checkPathInfo && info.length() > 1 && info.startsWith("/")) //$NON-NLS-1$ |
| { |
| this.componentRequested = true; |
| info = info.substring(1); |
| ComponentConfigurationDTO component = getComponentId(info); |
| if (component == null) |
| { |
| component = getComponentByName(info); |
| } |
| this.component = component; |
| if ( this.component != null ) |
| { |
| this.configurations.add(this.component); |
| } |
| } |
| else |
| { |
| this.componentRequested = false; |
| this.component = null; |
| |
| for(final ComponentDescriptionDTO d : this.descriptions) |
| { |
| if ( !runtime.isComponentEnabled(d) ) |
| { |
| disabled.add(d); |
| } |
| else |
| { |
| final Collection<ComponentConfigurationDTO> configs = runtime.getComponentConfigurationDTOs(d); |
| if ( configs.isEmpty() ) |
| { |
| disabled.add(d); |
| } |
| else |
| { |
| configurations.addAll(configs); |
| } |
| } |
| } |
| Collections.sort(configurations, Util.COMPONENT_COMPARATOR); |
| } |
| |
| request.setAttribute(WebConsolePlugin.this.getClass().getName(), this); |
| } |
| |
| protected ComponentConfigurationDTO getComponentId(final String componentIdPar) |
| { |
| try |
| { |
| final long componentId = Long.parseLong(componentIdPar); |
| for(final ComponentDescriptionDTO desc : this.descriptions) |
| { |
| for(final ComponentConfigurationDTO cfg : runtime.getComponentConfigurationDTOs(desc)) |
| { |
| if ( cfg.id == componentId ) |
| { |
| return cfg; |
| } |
| } |
| } |
| } |
| catch (NumberFormatException nfe) |
| { |
| // don't care |
| } |
| |
| return null; |
| } |
| |
| protected ComponentConfigurationDTO getComponentByName(final String names) |
| { |
| if (names.length() > 0) |
| { |
| final int slash = names.lastIndexOf('/'); |
| final String componentName; |
| final String pid; |
| long bundleId = -1; |
| if (slash > 0) |
| { |
| pid = names.substring(slash + 1); |
| final String firstPart = names.substring(0, slash); |
| final int bundleIndex = firstPart.indexOf('/'); |
| if ( bundleIndex == -1 ) |
| { |
| componentName = firstPart; |
| } |
| else |
| { |
| componentName = firstPart.substring(bundleIndex + 1); |
| try |
| { |
| bundleId = Long.valueOf(firstPart.substring(0, bundleIndex)); |
| } |
| catch ( final NumberFormatException nfe) |
| { |
| // wrong format |
| return null; |
| } |
| } |
| } |
| else |
| { |
| componentName = names; |
| pid = null; |
| } |
| |
| Collection<ComponentConfigurationDTO> components = null; |
| for(final ComponentDescriptionDTO d : this.descriptions) |
| { |
| if ( d.name.equals(componentName) && (bundleId == -1 || d.bundle.id == bundleId)) |
| { |
| components = runtime.getComponentConfigurationDTOs(d); |
| if ( components.isEmpty() ) |
| { |
| final ComponentConfigurationDTO cfg = new ComponentConfigurationDTO(); |
| cfg.description = d; |
| cfg.state = -1; |
| return cfg; |
| } |
| else |
| { |
| if (pid != null) |
| { |
| final Iterator<ComponentConfigurationDTO> i = components.iterator(); |
| while ( i.hasNext() ) |
| { |
| ComponentConfigurationDTO c = i.next(); |
| if ( pid.equals(c.description.configurationPid[0])) |
| { |
| return c; |
| } |
| |
| } |
| } |
| else if (components.size() > 0) |
| { |
| return components.iterator().next(); |
| } |
| |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| static RequestInfo getRequestInfo(final HttpServletRequest request) |
| { |
| return (RequestInfo) request.getAttribute(WebConsolePlugin.class.getName()); |
| } |
| } |