blob: 89bfc1fa3a5785ad55430f34726c585d054678d2 [file] [log] [blame]
/*
* 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.internal.configuration;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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.SimpleWebConsolePlugin;
import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.WebConsoleUtil;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
import org.apache.felix.webconsole.internal.misc.ServletSupport;
import org.apache.felix.webconsole.spi.ConfigurationHandler;
import org.apache.felix.webconsole.spi.ValidationException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.util.tracker.ServiceTracker;
/**
* The <code>ConfigManager</code> class is the Web Console plugin to
* manage configurations.
*/
public class ConfigManager extends SimpleWebConsolePlugin implements OsgiManagerPlugin, ServletSupport
{
private static final long serialVersionUID = 5021174538498622428L;
private static final String LABEL = "configMgr"; // was name //$NON-NLS-1$
private static final String TITLE = "%configMgr.pluginTitle"; //$NON-NLS-1$
private static final String CSS[] = { "/res/ui/config.css" }; //$NON-NLS-1$
static final String PID_FILTER = "pidFilter"; //$NON-NLS-1$
static final String PID = "pid"; //$NON-NLS-1$
static final String FACTORY_PID = "factoryPid"; //$NON-NLS-1$
static final String REFERER = "referer"; //$NON-NLS-1$
static final String FACTORY_CREATE = "factoryCreate"; //$NON-NLS-1$
static final String ACTION_CREATE = "create"; //$NON-NLS-1$
static final String ACTION_DELETE = "delete"; //$NON-NLS-1$
static final String ACTION_APPLY = "apply"; //$NON-NLS-1$
static final String ACTION_UPDATE = "update"; //$NON-NLS-1$
static final String ACTION_UNBIND = "unbind"; //$NON-NLS-1$
static final String PROPERTY_LIST = "propertylist"; //$NON-NLS-1$
static final String LOCATION = "$location"; //$NON-NLS-1$
static final String CONFIGURATION_ADMIN_NAME = "org.osgi.service.cm.ConfigurationAdmin"; //$NON-NLS-1$
static final String META_TYPE_NAME = "org.osgi.service.metatype.MetaTypeService"; //$NON-NLS-1$
public static final String UNBOUND_LOCATION = "??unbound:bundle/location"; //$NON-NLS-1$
// templates
private final String TEMPLATE;
// service tracker for SPI
private ServiceTracker<ConfigurationHandler, ConfigurationHandler> spiTracker;
/** Default constructor */
public ConfigManager() {
super(LABEL, TITLE, CATEGORY_OSGI, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/config.html" ); //$NON-NLS-1$
}
@Override
public void activate(final BundleContext bundleContext) {
super.activate(bundleContext);
this.spiTracker = new ServiceTracker<>(bundleContext, ConfigurationHandler.class.getName(), null);
this.spiTracker.open(true);
}
@Override
public void deactivate() {
if ( this.spiTracker != null ) {
this.spiTracker.close();
this.spiTracker = null;
}
super.deactivate();
}
/**
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException {
// service unavailable if config admin is not available
final ConfigAdminSupport cas = getConfigurationAdminSupport();
if ( cas == null ) {
response.sendError(503);
return;
}
// needed multiple times below
String pid = request.getParameter( ConfigManager.PID );
if ( pid == null ) {
String info = request.getPathInfo();
pid = WebConsoleUtil.urlDecode( info.substring( info.lastIndexOf( '/' ) + 1 ) );
}
// ignore this request if the PID is invalid / not provided
if ( pid == null || pid.length() == 0 || !ConfigurationUtil.isAllowedPid(pid)) {
response.sendError(400);
return;
}
// the filter to select part of the configurations
final String pidFilter = request.getParameter( PID_FILTER );
if ( pidFilter != null && !ConfigurationUtil.isAllowedPid(pidFilter) ) {
response.sendError(400);
return;
}
if ( request.getParameter( ACTION_APPLY ) != null ) {
if ( request.getParameter( ConfigManager.ACTION_DELETE ) != null ) { //$NON-NLS-1$
try {
cas.deleteConfiguration( pid );
Util.sendJsonOk(response);
} catch ( final ValidationException ve) {
response.sendError(400, ve.getMessage());
}
}
else
{
final String propertyList = request.getParameter( ConfigManager.PROPERTY_LIST ); //$NON-NLS-1$
if ( propertyList == null ) {
response.sendError(400);
return;
}
try {
String redirect = cas.applyConfiguration( request, pid, propertyList.split(","), ACTION_UPDATE.equals(request.getParameter(ACTION_APPLY)));
if (pidFilter != null) {
redirect = redirect.concat("?").concat(PID_FILTER).concat("=").concat(pidFilter);
}
WebConsoleUtil.sendRedirect(request, response, redirect);
} catch ( final ValidationException ve) {
response.sendError(400, ve.getMessage());
}
}
return;
}
// the configuration to operate on (to be created or "missing")
final Configuration config;
// should actually apply the configuration before redirecting
if ( request.getParameter( ACTION_CREATE ) != null ) {
config = ConfigurationUtil.getPlaceholderConfiguration( pid );
pid = config.getPid();
} else {
config = cas.findConfiguration( pid );
}
// check for configuration unbinding
if ( request.getParameter( ACTION_UNBIND ) != null )
{
if ( config != null && config.getBundleLocation() != null ) {
config.setBundleLocation( UNBOUND_LOCATION );
}
Util.sendJsonOk(response);
return;
}
// send the result
response.setContentType( "application/json" ); //$NON-NLS-1$
response.setCharacterEncoding( "UTF-8" ); //$NON-NLS-1$
final Locale loc = Util.getLocale( request );
final String locale = ( loc != null ) ? loc.toString() : null;
cas.getJsonSupport().printConfigurationJson( response.getWriter(), pid, config, pidFilter, locale );
}
/**
* @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 {
// check for "post" requests from previous versions
if ( "true".equals(request.getParameter("post")) ) { //$NON-NLS-1$ //$NON-NLS-2$
this.doPost(request, response);
return;
}
final String info = request.getPathInfo();
// let's check for a JSON request
if ( info.endsWith( ".json" ) ) //$NON-NLS-1$
{
response.setContentType( "application/json" ); //$NON-NLS-1$
response.setCharacterEncoding( "UTF-8" ); //$NON-NLS-1$
// after last slash and without extension
String pid = info.substring( info.lastIndexOf( '/' ) + 1, info.length() - 5 );
// check whether the PID is actually a filter for the selection
// of configurations to display, if the filter correctly converts
// into an OSGi filter, we use it to select configurations
// to display
String pidFilter = request.getParameter( PID_FILTER );
if ( pidFilter == null )
{
pidFilter = pid;
}
try
{
getBundleContext().createFilter( pidFilter );
// if the pidFilter was set from the PID, clear the PID
if ( pid == pidFilter )
{
pid = null;
}
}
catch ( InvalidSyntaxException ise )
{
// its OK, if the PID is just a single PID
pidFilter = null;
}
// check both PID and PID filter
if ( pid != null && !ConfigurationUtil.isAllowedPid(pid) )
{
response.sendError(400);
}
if ( pidFilter != null && !ConfigurationUtil.isAllowedPid(pidFilter) )
{
response.sendError(400);
}
final Locale loc = Util.getLocale( request );
final String locale = ( loc != null ) ? loc.toString() : null;
final PrintWriter pw = response.getWriter();
pw.write( "[" ); //$NON-NLS-1$
final ConfigAdminSupport ca = this.getConfigurationAdminSupport();
if ( ca != null )
{
// create filter
final StringBuffer sb = new StringBuffer();
if ( pid != null && pidFilter != null)
{
sb.append("(&"); //$NON-NLS-1$
}
if ( pid != null )
{
sb.append('(');
sb.append(Constants.SERVICE_PID);
sb.append('=');
sb.append(pid);
sb.append(')');
}
if ( pidFilter != null )
{
sb.append(pidFilter);
}
if ( pid != null && pidFilter != null)
{
sb.append(')');
}
final String filter = sb.toString();
try
{
// we use listConfigurations to not create configuration
// objects persistently without the user providing actual
// configuration
final Configuration[] configs = ca.listConfigurations( filter );
boolean printComma = false;
for(int i=0; configs != null && i<configs.length; i++)
{
final Configuration config = configs[i];
if ( config != null )
{
if ( printComma )
{
pw.print( ',' );
}
ca.getJsonSupport().printConfigurationJson( pw, config.getPid(), config, null, locale );
printComma = true;
}
}
}
catch ( final InvalidSyntaxException ise )
{
// should print message
// however this should not happen as we checked the filter before
}
catch ( final IOException ioe )
{
// should print message
}
}
pw.write( "]" ); //$NON-NLS-1$
// nothing more to do
return;
}
super.doGet( request, response );
}
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
{
// extract the configuration PID from the request path
String pid = request.getPathInfo().substring(this.getLabel().length() + 1);
if ( pid.length() == 0 ) {
pid = null;
} else {
pid = pid.substring( pid.lastIndexOf( '/' ) + 1 );
}
// check whether the PID is actually a filter for the selection
// of configurations to display, if the filter correctly converts
// into an OSGi filter, we use it to select configurations
// to display
String pidFilter = request.getParameter( PID_FILTER );
if ( pidFilter == null )
{
pidFilter = pid;
}
if ( pidFilter != null )
{
try
{
getBundleContext().createFilter( pidFilter );
// if the pidFilter was set from the PID, clear the PID
if ( pid == pidFilter )
{
pid = null;
}
}
catch ( InvalidSyntaxException ise )
{
// its OK, if the PID is just a single PID
pidFilter = null;
}
}
// check both PID and PID filter
if ( pid != null && !ConfigurationUtil.isAllowedPid(pid) )
{
response.sendError(400);
}
if ( pidFilter != null && !ConfigurationUtil.isAllowedPid(pidFilter) )
{
response.sendError(400);
}
final Locale loc = Util.getLocale( request );
final String locale = ( loc != null ) ? loc.toString() : null;
StringWriter json = new StringWriter();
JSONWriter jw = new JSONWriter(json);
jw.object();
final ConfigAdminSupport cas = getConfigurationAdminSupport();
// check for osgi installer plugin
@SuppressWarnings("unchecked")
final Map<String, Object> labelMap = (Map<String, Object>) request.getAttribute(WebConsoleConstants.ATTR_LABEL_MAP);
jw.key("jsonsupport").value( labelMap.containsKey("osgi-installer-config-printer") ); //$NON-NLS-1$
final boolean hasMetatype = cas.getMetaTypeSupport() != null;
jw.key("status").value( cas != null ? Boolean.TRUE : Boolean.FALSE); //$NON-NLS-1$
jw.key("metatype").value( hasMetatype ? Boolean.TRUE : Boolean.FALSE); //$NON-NLS-1$
boolean hasConfigs = true;
if ( cas != null )
{
hasConfigs = cas.getJsonSupport().listConfigurations( jw, pidFilter, locale, loc);
cas.getJsonSupport().listFactoryConfigurations( jw, pidFilter, locale );
}
if ( !hasConfigs && !hasMetatype && cas != null ) {
jw.key("noconfigs").value(true); //$NON-NLS-1$
} else {
jw.key("noconfigs").value(false); //$NON-NLS-1$
}
jw.endObject();
// if a configuration is addressed, display it immediately
if ( request.getParameter( ACTION_CREATE ) != null && pid != null ) {
pid = ConfigurationUtil.getPlaceholderPid();
}
// prepare variables
final String referer = request.getParameter( REFERER );
final boolean factoryCreate = "true".equals( request.getParameter(FACTORY_CREATE) ); //$NON-NLS-1$
final Map<String, Object> vars = ( ( Map<String, Object> ) WebConsoleUtil.getVariableResolver( request ) );
vars.put( "__data__", json.toString() ); //$NON-NLS-1$
vars.put( "selectedPid", pid != null ? pid : "" ); //$NON-NLS-1$ //$NON-NLS-2$
vars.put( "configurationReferer", referer != null ? referer : "" ); //$NON-NLS-1$ //$NON-NLS-2$
vars.put( "factoryCreate", Boolean.valueOf(factoryCreate) ); //$NON-NLS-1$
vars.put( "param.apply", ACTION_APPLY ); //$NON-NLS-1$
vars.put( "param.create", ACTION_CREATE ); //$NON-NLS-1$
vars.put( "param.unbind", ACTION_UNBIND ); //$NON-NLS-1$
vars.put( "param.delete", ACTION_DELETE ); //$NON-NLS-1$
vars.put( "param.propertylist", PROPERTY_LIST ); //$NON-NLS-1$
vars.put( "param.pidFilter", PID_FILTER ); //$NON-NLS-1$
response.getWriter().print(TEMPLATE);
}
private ConfigAdminSupport getConfigurationAdminSupport() {
Object configurationAdmin = getService( CONFIGURATION_ADMIN_NAME );
if ( configurationAdmin != null ) {
final List<ConfigurationHandler> handlers = new ArrayList<>();
Object[] services = this.spiTracker.getServices();
if (services != null) {
for(final Object o : services) {
handlers.add((ConfigurationHandler)o);
}
}
return new ConfigAdminSupport( this, configurationAdmin, handlers );
}
return null;
}
@Override
public BundleContext getBundleContext() {
return super.getBundleContext();
}
}