blob: a75cf22187081671435ad1434d6f48d529e4e532 [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.dm.impl.metatype;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.apache.felix.dm.Logger;
import org.apache.felix.dm.PropertyMetaData;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.log.LogService;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;
/**
* When a ConfigurationDepdendency is configured with properties metadata, we provide
* a specific ManagedService which also implements the MetaTypeProvider interface. This interface
* allows the MetaTypeService to retrieve our properties metadata, which will then be handled by webconsole.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class MetaTypeProviderImpl implements MetaTypeProvider, ManagedService, ManagedServiceFactory {
private ManagedService m_managedServiceDelegate;
private ManagedServiceFactory m_managedServiceFactoryDelegate;
private ArrayList<PropertyMetaData> m_propertiesMetaData = new ArrayList<>();
private String m_description;
private String m_heading;
private String m_localization;
private HashMap<String, Properties> m_localesProperties = new HashMap<>();
private Logger m_logger;
private BundleContext m_bctx;
private String m_pid;
public MetaTypeProviderImpl(String pid, BundleContext ctx, Logger logger, ManagedService msDelegate, ManagedServiceFactory msfDelegate) {
m_pid = pid;
m_bctx = ctx;
m_logger = logger;
m_managedServiceDelegate = msDelegate;
m_managedServiceFactoryDelegate = msfDelegate;
// Set the default localization file base name (see core specification, in section Localization on page 68).
// By default, this file can be stored in OSGI-INF/l10n/bundle.properties (and corresponding localized version
// in OSGI-INF/l10n/bundle_en_GB_welsh.properties, OSGI-INF/l10n/bundle_en_GB.properties, etc ...
// This default localization property file name can be overriden using the PropertyMetaData.setLocalization method.
m_localization = (String) m_bctx.getBundle().getHeaders().get(Constants.BUNDLE_LOCALIZATION);
if (m_localization == null) {
m_localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
}
}
@SuppressWarnings("unchecked")
public MetaTypeProviderImpl(MetaTypeProviderImpl prototype, ManagedService msDelegate, ManagedServiceFactory msfDelegate) {
m_pid = prototype.m_pid;
m_bctx = prototype.m_bctx;
m_logger = prototype.m_logger;
m_localization = prototype.m_localization;
m_propertiesMetaData = prototype.m_propertiesMetaData != null ? (ArrayList<PropertyMetaData>) prototype.m_propertiesMetaData.clone() : null;
m_description = prototype.m_description;
m_heading = prototype.m_heading;
m_localization = prototype.m_localization;
m_localesProperties = prototype.m_localesProperties != null ? (HashMap<String, Properties>) prototype.m_localesProperties.clone() : null;
m_managedServiceDelegate = msDelegate;
m_managedServiceFactoryDelegate = msfDelegate;
}
/**
* Registers the metatype information of a given configuration property
* @param property
*/
public void add(PropertyMetaData property) {
m_propertiesMetaData.add(property);
}
/**
* A human readable description of the PID this annotation is associated with. Example: "Configuration for the PrinterService bundle".
* @return A human readable description of the PID this annotation is associated with (may be localized)
*/
public void setDescription(String description) {
m_description = description;
}
/**
* The label used to display the tab name (or section) where the properties are displayed. Example: "Printer Service".
* @return The label used to display the tab name where the properties are displayed (may be localized)
*/
public void setName(String heading) {
m_heading = heading;
}
/**
* Points to the basename of the Properties file that can localize the Meta Type informations.
* By default, (e.g. <code>setLocalization("person")</code> would match person_du_NL.properties in the root bundle directory.
* The default localization base name for the properties is OSGI-INF/l10n/bundle, but can
* be overridden by the manifest Bundle-Localization header (see core specification, in section Localization on page 68).
*/
public void setLocalization(String path) {
if (path.endsWith(".properties")) {
throw new IllegalArgumentException(
"path must point to the base name of the propertie file, "
+ "excluding local suffixes. For example: "
+ "foo/bar/person is valid and matches the property file \"foo/bar/person_bundle_en_GB_welsh.properties\"");
}
m_localization = path.startsWith("/") ? path.substring(1) : path;
}
// --------------- MetaTypeProvider interface -------------------------------------------------
/**
* Returns all the Locales our bundle is containing. For instance, if our bundle contains the following localization files:
* OSGI-INF/l10n/bundle_en_GB_welsh.properties and OSGI-INF/l10n/bundle_en_GB.properties, then this method will return
* "en_GB", "en_GB_welsh" ...
* @return the list of Locale supported by our bundle.
*/
@SuppressWarnings("rawtypes")
public String[] getLocales() {
int lastSlash = m_localization.lastIndexOf("/");
String path = (lastSlash == -1) ? "/" : ("/" + m_localization.substring(0, lastSlash - 1));
String base = (lastSlash == -1) ? m_localization : m_localization.substring(lastSlash + 1);
Enumeration e = m_bctx.getBundle().findEntries(path,
base + "*.properties", false);
if (e == null) {
return null;
}
TreeSet<String> set = new TreeSet<>();
while (e.hasMoreElements()) {
// We have found a locale property file in the form of "path/file[_language[_ country[_variation]].properties"
// And now, we have to get the "language[_country[_variation]]" part ...
URL url = (URL) e.nextElement();
String name = url.getPath();
name = name.substring(name.lastIndexOf("/") + 1);
int underscore = name.indexOf("_");
if (underscore != -1) {
name = name.substring(underscore + 1, name.length() - ".properties".length());
}
if (name.length() > 0) {
set.add(name);
}
}
String[] locales = (String[]) set.toArray(new String[set.size()]);
return locales.length == 0 ? null : locales;
}
/**
* Returns the ObjectClassDefinition for a given Pid/Locale.
*/
public ObjectClassDefinition getObjectClassDefinition(String id, String locale) {
try {
// Check if the id matches our PID
if (!id.equals(m_pid)) {
m_logger.log(LogService.LOG_ERROR, "id " + id + " does not match pid " + m_pid);
return null;
}
Properties localeProperties = getLocaleProperties(locale);
return new ObjectClassDefinitionImpl(m_pid, m_heading,
m_description, m_propertiesMetaData, new Resource(localeProperties));
}
catch (Throwable t) {
m_logger.log(
Logger.LOG_ERROR,
"Unexpected exception while geting ObjectClassDefinition for " + id + " (locale="
+ locale + ")", t);
return null;
}
}
/**
* We also implements the ManagedService and we just delegates the configuration handling to
* our associated ConfigurationDependency.
*/
public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
m_managedServiceDelegate.updated(properties);
}
/**
* Gets the properties for a given Locale.
* @param locale
* @return
* @throws IOException
*/
private synchronized Properties getLocaleProperties(String locale) throws IOException {
locale = locale == null ? Locale.getDefault().toString() : locale;
Properties properties = (Properties) m_localesProperties.get(locale);
if (properties == null) {
properties = new Properties();
URL url = m_bctx.getBundle().getEntry(m_localization + ".properties");
if (url != null) {
loadLocale(properties, url);
}
String path = m_localization;
StringTokenizer tok = new StringTokenizer(locale, "_");
while (tok.hasMoreTokens()) {
path += "_" + tok.nextToken();
url = m_bctx.getBundle().getEntry(path + ".properties");
if (url != null) {
properties = new Properties(properties);
loadLocale(properties, url);
}
}
m_localesProperties.put(locale, properties);
}
return properties;
}
/**
* Loads a Locale Properties file.
* @param properties
* @param url
* @throws IOException
*/
private void loadLocale(Properties properties, URL url) throws IOException {
InputStream in = null;
try {
in = url.openStream();
properties.load(in);
}
finally {
if (in != null) {
try {
in.close();
}
catch (IOException ignored) {
}
}
}
}
// ManagedServiceFactory implementation
public void deleted(String pid) {
m_managedServiceFactoryDelegate.deleted(pid);
}
public String getName() {
return m_pid;
}
public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
m_managedServiceFactoryDelegate.updated(pid, properties);
}
}