/******************************************************************************* * Copyright (c) 2011, 2016 Eurotech and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eurotech * Red Hat Inc - Fix service leak * - fix default value lookup *******************************************************************************/ package org.eclipse.kura.core.configuration.util; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBException; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLStreamException; import org.eclipse.kura.KuraException; import org.eclipse.kura.configuration.Password; import org.eclipse.kura.configuration.metatype.AD; import org.eclipse.kura.configuration.metatype.Designate; import org.eclipse.kura.configuration.metatype.MetaData; import org.eclipse.kura.configuration.metatype.OCD; import org.eclipse.kura.configuration.metatype.Scalar; import org.eclipse.kura.core.configuration.metatype.Tmetadata; import org.eclipse.kura.core.configuration.metatype.Tocd; import org.eclipse.kura.core.util.IOUtil; import org.eclipse.kura.crypto.CryptoService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.osgi.service.metatype.MetaTypeInformation; import org.osgi.service.metatype.MetaTypeService; import org.osgi.service.metatype.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class to handle the loading of the meta-information * of a bundle or one of its Services/Components. */ public class ComponentUtil { private static final Logger s_logger = LoggerFactory.getLogger(ComponentUtil.class); /** * Returns a Map with all the MetaType Object Class Definitions contained in the bundle. * * @param ctx * @param bnd * @return */ public static Map<String, Tmetadata> getMetadata(BundleContext ctx, Bundle bnd) { final Map<String, Tmetadata> bundleMetadata = new HashMap<String, Tmetadata>(); final ServiceReference<MetaTypeService> ref = ctx.getServiceReference(MetaTypeService.class); final MetaTypeService metaTypeService = ctx.getService(ref); try { final MetaTypeInformation mti = metaTypeService.getMetaTypeInformation(bnd); if (mti != null) { final List<String> pids = new ArrayList<String>(); pids.addAll(Arrays.asList(mti.getPids())); pids.addAll(Arrays.asList(mti.getFactoryPids())); if (pids != null) { for (String pid : pids) { final Tmetadata metadata; try { metadata = readMetadata(bnd, pid); if (metadata != null) { bundleMetadata.put(pid, metadata); } } catch (Exception e) { // ignore: Metadata for the specified pid is not found s_logger.warn("Error loading Metadata for pid " + pid, e); } } } } } finally { ctx.ungetService(ref); } return bundleMetadata; } /** * Returns the Designate for the given pid */ public static Designate getDesignate(Tmetadata metadata, String pid) { List<Designate> designates = metadata.getDesignate(); for (Designate designate : designates) { if (designate.getPid().equals(pid)) { return designate; } } return null; } /** * Returns the Designate for the given pid */ public static OCD getOCD(Tmetadata metadata, String pid) { if (metadata.getOCD() != null && metadata.getOCD().size() > 0) { for (OCD ocd : metadata.getOCD()) { if (ocd.getId() != null && ocd.getId().equals(pid)) { return ocd; } } } return null; } /** * Returns a Map with all the MetaType Object Class Definitions contained in the bundle. * * @param ctx * @param bnd * @return */ public static Map<String, OCD> getObjectClassDefinition(BundleContext ctx, Bundle bnd) { Map<String, OCD> bundleDefaults = new HashMap<String, OCD>(); ServiceReference<MetaTypeService> ref = ctx.getServiceReference(MetaTypeService.class); MetaTypeService metaTypeService = ctx.getService(ref); MetaTypeInformation mti = metaTypeService.getMetaTypeInformation(bnd); if (mti != null) { String[] pids = mti.getPids(); if (pids != null) { for (String pid : pids) { OCD ocd = null; try { ocd = readObjectClassDefinition(bnd, pid); if (ocd != null) { bundleDefaults.put(pid, ocd); } } catch (Exception e) { // ignore: OCD for the specified pid is not found s_logger.warn("Error loading OCD for pid " + pid, e); } } } } return bundleDefaults; } /** * Returns the ObjectClassDefinition for the provided Service pid * as returned by the native OSGi MetaTypeService. * The returned ObjectClassDefinition is a thick object tied to * OSGi framework which masks certain attributes of the OCD XML * file - like required and min/max values - but which exposes * business logic like the validate method for each attribute. * * @param ctx * @param pid * @return */ public static ObjectClassDefinition getObjectClassDefinition(BundleContext ctx, String pid) { // ServiceReference<MetaTypeService> ref = ctx.getServiceReference(MetaTypeService.class); MetaTypeService metaTypeService = ctx.getService(ref); MetaTypeInformation mti = metaTypeService.getMetaTypeInformation(ctx.getBundle()); ObjectClassDefinition ocd = null; try { if (mti != null) { ocd = mti.getObjectClassDefinition(pid, null); // default locale } } catch (IllegalArgumentException iae) { // ignore: OCD for the specified pid is not found } return ocd; } /** * Returned the Tmetadata as parsed from the XML file. * The returned Tmetadata is just an object representation of the Metadata * element contained in the XML MetaData file and it does not * contain any extra post-processing of the loaded information. * * @param ctx * @param pid * ID of the service whose OCD should be loaded * @return * @throws IOException * @throws JAXBException * @throws XMLStreamException * @throws FactoryConfigurationError */ public static Tmetadata readMetadata(Bundle bundle, String pid) throws IOException, Exception, XMLStreamException, FactoryConfigurationError { Tmetadata metaData = null; StringBuilder sbMetatypeXmlName = new StringBuilder(); sbMetatypeXmlName.append("OSGI-INF/metatype/").append(pid).append(".xml"); String metatypeXmlName = sbMetatypeXmlName.toString(); String metatypeXml = IOUtil.readResource(bundle, metatypeXmlName); if (metatypeXml != null) { metaData = XmlUtil.unmarshal(metatypeXml, Tmetadata.class); } return metaData; } /** * Returned the ObjectClassDefinition as parsed from the XML file. * The returned OCD is just an object representation of the OCD * element contained in the XML MetaData file and it does not * contain any extra post-processing of the loaded information. * * @param resourceUrl * Url of the MetaData XML file which needs to be loaded * @return * @throws IOException * @throws JAXBException * @throws XMLStreamException * @throws FactoryConfigurationError */ public static OCD readObjectClassDefinition(URL resourceUrl) throws IOException, XMLStreamException, FactoryConfigurationError, Exception { OCD ocd = null; String metatypeXml = IOUtil.readResource(resourceUrl); if (metatypeXml != null) { MetaData metaData = XmlUtil.unmarshal(metatypeXml, MetaData.class); if (metaData.getOCD() != null && metaData.getOCD().size() > 0) { ocd = metaData.getOCD().get(0); } else { s_logger.warn("Cannot find OCD for component with url: {}", resourceUrl.toString()); } } return ocd; } /** * Returned the ObjectClassDefinition as parsed from the XML file. * The returned OCD is just an object representation of the OCD * element contained in the XML MetaData file and it does not * contain any extra post-processing of the loaded information. * * @param pid * ID of the service whose OCD should be loaded * @return * @throws IOException * @throws JAXBException * @throws XMLStreamException * @throws FactoryConfigurationError */ public static Tocd readObjectClassDefinition(String pid) throws IOException, Exception, XMLStreamException, FactoryConfigurationError { Tocd ocd = null; StringBuilder sbMetatypeXmlName = new StringBuilder(); sbMetatypeXmlName.append("OSGI-INF/metatype/").append(pid).append(".xml"); String metatypeXmlName = sbMetatypeXmlName.toString(); String metatypeXml = IOUtil.readResource(metatypeXmlName); if (metatypeXml != null) { Tmetadata metaData = XmlUtil.unmarshal(metatypeXml, Tmetadata.class); if (metaData.getOCD() != null && metaData.getOCD().size() > 0) { ocd = (Tocd) metaData.getOCD().get(0); } else { s_logger.warn("Cannot find OCD for component with pid: {}", pid); } } return ocd; } /** * Returned the ObjectClassDefinition as parsed from the XML file. * The returned OCD is just an object representation of the OCD * element contained in the XML MetaData file and it does not * contain any extra post-processing of the loaded information. * * @param ctx * @param pid * ID of the service whose OCD should be loaded * @return * @throws IOException * @throws JAXBException * @throws XMLStreamException * @throws FactoryConfigurationError */ public static Tocd readObjectClassDefinition(Bundle bundle, String pid) throws IOException, Exception, XMLStreamException, FactoryConfigurationError { Tocd ocd = null; StringBuilder sbMetatypeXmlName = new StringBuilder(); sbMetatypeXmlName.append("OSGI-INF/metatype/").append(pid).append(".xml"); String metatypeXmlName = sbMetatypeXmlName.toString(); String metatypeXml = IOUtil.readResource(bundle, metatypeXmlName); if (metatypeXml != null) { Tmetadata metaData = XmlUtil.unmarshal(metatypeXml, Tmetadata.class); if (metaData.getOCD() != null && metaData.getOCD().size() > 0) { ocd = (Tocd) metaData.getOCD().get(0); } } return ocd; } public static Map<String, Object> getDefaultProperties(OCD ocd, ComponentContext ctx) throws KuraException { // // reconcile by looping through the ocd properties Map<String, Object> defaults = null; defaults = new HashMap<String, Object>(); if (ocd != null) { List<AD> attrDefs = ocd.getAD(); if (attrDefs != null) { for (AD attrDef : attrDefs) { String id = attrDef.getId(); Object defaultValue = getDefaultValue(attrDef, ctx); if (defaults.get(id) == null && defaultValue != null) { defaults.put(id, defaultValue); } } } } return defaults; } private static Object getDefaultValue(AD attrDef, ComponentContext ctx) { // get the default value string from the AD // then split it by comma-separate list // keeping in mind the possible escape sequence "\," String defaultValue = attrDef.getDefault(); if (defaultValue == null || defaultValue.length() == 0) { return null; } Object[] objectValues = null; Scalar type = attrDef.getType(); if (type != null) { // split the default value in separate string // if cardinality is greater than 0 or abs(1) String[] defaultValues = new String[] { defaultValue }; int cardinality = attrDef.getCardinality(); if (cardinality != 0 || cardinality != 1 || cardinality != -1) { defaultValues = StringUtil.splitValues(defaultValue); } // convert string values into object values objectValues = getObjectValue(type, defaultValues, ctx); if (cardinality == 0 || cardinality == 1 || cardinality == -1) { // return one single object // if cardinality is 0 or abs(1) return objectValues[0]; } } else { s_logger.warn("Unknown type for attribute {}", attrDef.getId()); } return objectValues; } private static Object[] getObjectValue(Scalar type, String[] defaultValues, ComponentContext ctx) { List<Object> values = new ArrayList<Object>(); switch (type) { case BOOLEAN: for (String value : defaultValues) { values.add(Boolean.valueOf(value)); } return values.toArray(new Boolean[] {}); case BYTE: for (String value : defaultValues) { values.add(Byte.valueOf(value)); } return values.toArray(new Byte[] {}); case CHAR: for (String value : defaultValues) { values.add(Character.valueOf(value.charAt(0))); } return values.toArray(new Character[] {}); case DOUBLE: for (String value : defaultValues) { values.add(Double.valueOf(value)); } return values.toArray(new Double[] {}); case FLOAT: for (String value : defaultValues) { values.add(Float.valueOf(value)); } return values.toArray(new Float[] {}); case INTEGER: for (String value : defaultValues) { values.add(Integer.valueOf(value)); } return values.toArray(new Integer[] {}); case LONG: for (String value : defaultValues) { values.add(Long.valueOf(value)); } return values.toArray(new Long[] {}); case SHORT: for (String value : defaultValues) { values.add(Short.valueOf(value)); } return values.toArray(new Short[] {}); case PASSWORD: ServiceReference<CryptoService> sr = ctx.getBundleContext().getServiceReference(CryptoService.class); CryptoService cryptoService = ctx.getBundleContext().getService(sr); for (String value : defaultValues) { try { values.add(new Password(cryptoService.encryptAes(value.toCharArray()))); } catch (Exception e) { values.add(new Password(value)); } } return values.toArray(new Password[] {}); case STRING: return defaultValues; } return null; } }