Index: src/main/java/org/apache/karaf/jaas/cmencrypt/CmEncNamespaceHandler.java =================================================================== --- src/main/java/org/apache/karaf/jaas/cmencrypt/CmEncNamespaceHandler.java (revision 0) +++ src/main/java/org/apache/karaf/jaas/cmencrypt/CmEncNamespaceHandler.java (revision 0) @@ -0,0 +1,349 @@ +/** + * 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.karaf.jaas.cmencrypt; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.w3c.dom.CharacterData; +import org.w3c.dom.Comment; +import org.w3c.dom.Element; +import org.w3c.dom.EntityReference; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.apache.aries.blueprint.ComponentDefinitionRegistry; +import org.apache.aries.blueprint.NamespaceHandler; +import org.apache.aries.blueprint.ParserContext; +import org.apache.aries.blueprint.compendium.cm.CmNamespaceHandler; +import org.apache.aries.blueprint.compendium.cm.ManagedObjectManager; +import org.apache.aries.blueprint.parser.Parser; +import org.apache.aries.blueprint.parser.ParserContextImpl; +import org.apache.aries.blueprint.ext.impl.ExtNamespaceHandler; +import org.apache.aries.blueprint.ext.PlaceholdersUtils; +import org.apache.aries.blueprint.mutable.MutableBeanMetadata; +import org.apache.aries.blueprint.mutable.MutableCollectionMetadata; +import org.apache.aries.blueprint.mutable.MutableComponentMetadata; +import org.apache.aries.blueprint.mutable.MutableIdRefMetadata; +import org.apache.aries.blueprint.mutable.MutableMapMetadata; +import org.apache.aries.blueprint.mutable.MutableRefMetadata; +import org.apache.aries.blueprint.mutable.MutableValueMetadata; +import org.jasypt.encryption.StringEncryptor; +import org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.osgi.service.blueprint.reflect.BeanMetadata; +import org.osgi.service.blueprint.reflect.BeanProperty; +import org.osgi.service.blueprint.reflect.CollectionMetadata; +import org.osgi.service.blueprint.reflect.ComponentMetadata; +import org.osgi.service.blueprint.reflect.IdRefMetadata; +import org.osgi.service.blueprint.reflect.Metadata; +import org.osgi.service.blueprint.reflect.RefMetadata; +import org.osgi.service.blueprint.reflect.ValueMetadata; +import org.osgi.service.cm.ConfigurationAdmin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CmEncNamespaceHandler implements NamespaceHandler { + + public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0"; + public static final String BLUEPRINT_CM_NAMESPACE_1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-cmenc/v1.0.0"; + public static final String BLUEPRINT_CM_NAMESPACE_1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-cmenc/v1.1.0"; + + public static final String PROPERTY_PLACEHOLDER_ELEMENT = "property-placeholder"; + public static final String DEFAULT_PROPERTIES_ELEMENT = "default-properties"; + public static final String PROPERTY_ELEMENT = "property"; + + public static final String ID_ATTRIBUTE = "id"; + public static final String PERSISTENT_ID_ATTRIBUTE = "persistent-id"; + public static final String PLACEHOLDER_PREFIX_ATTRIBUTE = "placeholder-prefix"; + public static final String PLACEHOLDER_SUFFIX_ATTRIBUTE = "placeholder-suffix"; + public static final String DEFAULTS_REF_ATTRIBUTE = "defaults-ref"; + public static final String UPDATE_STRATEGY_ATTRIBUTE = "update-strategy"; + + public static final String AUTO_EXPORT_DISABLED = "disabled"; + + private static final String MANAGED_OBJECT_MANAGER_NAME = "org.apache.aries.managedObjectManager"; + + private final Logger LOGGER = LoggerFactory.getLogger(CmEncNamespaceHandler.class); + + // This property is static but it should be ok since there will be only a single instance + // of this class for the bundle + private static ConfigurationAdmin configAdmin; + + private int idCounter; + + public int getIdCounter() { + return idCounter; + } + + public void setIdCounter(int idCounter) { + this.idCounter = idCounter; + } + + public static ConfigurationAdmin getConfigAdmin() { + return configAdmin; + } + + public void setConfigAdmin(ConfigurationAdmin configAdmin) { + this.configAdmin = configAdmin; + } + + @Override + public URL getSchemaLocation(String s) { + return getClass().getClassLoader().getResource("/org/apache/karaf/jaas/cmencrypt/cmencrypt.xsd"); + } + + @Override + public Set getManagedClasses() { + return null; //TODO + } + + @Override + public Metadata parse(Element element, ParserContext context) { + LOGGER.debug("Parsing element {{}}{}", element.getNamespaceURI(), element.getLocalName()); + ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry(); + registerManagedObjectManager(context, registry); + if (nodeNameEquals(element, PROPERTY_PLACEHOLDER_ELEMENT)) { + return parsePropertyPlaceholder(context, element); + } else { + throw new ComponentDefinitionException("Unsupported element: " + element.getNodeName()); + } + } + + private ComponentMetadata parsePropertyPlaceholder(ParserContext context, Element element) { + MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class); + + metadata.addArgument(createRef(context, element.getAttribute("decryptor")), StringEncryptor.class.getName(), 0); + metadata.setProcessor(true); + metadata.setId(getId(context, element)); + metadata.setScope(BeanMetadata.SCOPE_SINGLETON); + metadata.setRuntimeClass(EncryptedPropertyPlaceholder.class); + metadata.setInitMethod("init"); + metadata.setDestroyMethod("destroy"); + metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer")); + metadata.addProperty("configAdmin", createConfigAdminProxy(context)); + metadata.addProperty("persistentId", createValue(context, element.getAttribute(PERSISTENT_ID_ATTRIBUTE))); + String prefix = element.hasAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE) ? element.getAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE) : "${"; + metadata.addProperty("placeholderPrefix", createValue(context, prefix)); + String suffix = element.hasAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE) ? element.getAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE) : "}"; + metadata.addProperty("placeholderSuffix", createValue(context, suffix)); + String defaultsRef = element.hasAttribute(DEFAULTS_REF_ATTRIBUTE) ? element.getAttribute(DEFAULTS_REF_ATTRIBUTE) : null; + if (defaultsRef != null) { + metadata.addProperty("defaultProperties", createRef(context, defaultsRef)); + } + String ignoreMissingLocations = extractIgnoreMissingLocations(element); + if (ignoreMissingLocations != null) { + metadata.addProperty("ignoreMissingLocations", createValue(context, ignoreMissingLocations)); + } + String systemProperties = extractSystemPropertiesAttribute(element); + if (systemProperties == null) { + systemProperties = ExtNamespaceHandler.SYSTEM_PROPERTIES_NEVER; + } + metadata.addProperty("systemProperties", createValue(context, systemProperties)); + String updateStrategy = element.getAttribute(UPDATE_STRATEGY_ATTRIBUTE); + if (updateStrategy != null) { + metadata.addProperty("updateStrategy", createValue(context, updateStrategy)); + } + metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME)); + // Parse elements + List locations = new ArrayList(); + NodeList nl = element.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + Element e = (Element) node; + if (BLUEPRINT_CM_NAMESPACE_1_0.equals(e.getNamespaceURI()) || BLUEPRINT_CM_NAMESPACE_1_1.equals(e.getNamespaceURI())) { + if (nodeNameEquals(e, DEFAULT_PROPERTIES_ELEMENT)) { + if (defaultsRef != null) { + throw new ComponentDefinitionException( + "Only one of " + DEFAULTS_REF_ATTRIBUTE + " attribute or " + DEFAULT_PROPERTIES_ELEMENT + " element is allowed"); + } + Metadata props = parseDefaultProperties(context, metadata, e); + metadata.addProperty("defaultProperties", props); + } + } else if (ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_0.equals(e.getNamespaceURI())) { + if (nodeNameEquals(e, ExtNamespaceHandler.LOCATION_ELEMENT)) { + locations.add(getTextValue(e)); + } + } //else if (ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1.equals(e.getNamespaceURI())) { + //if (nodeNameEquals(e, ExtNamespaceHandler.LOCATION_ELEMENT)) { + // locations.add(getTextValue(e)); + //} + //} + } + } + if (!locations.isEmpty()) { + metadata.addProperty("locations", createList(context, locations)); + } + + PlaceholdersUtils.validatePlaceholder(metadata, context.getComponentDefinitionRegistry()); + + return metadata; + } + + @Override + public ComponentMetadata decorate(Node node, ComponentMetadata componentMetadata, ParserContext context) { + return null; //TODO + } + + private Metadata createConfigAdminProxy(ParserContext context) { + MutableBeanMetadata bean = context.createMetadata(MutableBeanMetadata.class); + bean.setRuntimeClass(CmNamespaceHandler.class); + bean.setFactoryMethod("getConfigAdmin"); + bean.setActivation(MutableBeanMetadata.ACTIVATION_LAZY); + bean.setScope(MutableBeanMetadata.SCOPE_PROTOTYPE); + return bean; + } + + private void registerManagedObjectManager(ParserContext context, ComponentDefinitionRegistry registry) { + if (registry.getComponentDefinition(MANAGED_OBJECT_MANAGER_NAME) == null) { + MutableBeanMetadata beanMetadata = context.createMetadata(MutableBeanMetadata.class); + beanMetadata.setScope(BeanMetadata.SCOPE_SINGLETON); + beanMetadata.setId(MANAGED_OBJECT_MANAGER_NAME); + beanMetadata.setRuntimeClass(ManagedObjectManager.class); + registry.registerComponentDefinition(beanMetadata); + } + } + + private static ValueMetadata createValue(ParserContext context, String value) { + return createValue(context, value, null); + } + + private static ValueMetadata createValue(ParserContext context, String value, String type) { + MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class); + m.setStringValue(value); + m.setType(type); + return m; + } + + private static RefMetadata createRef(ParserContext context, String value) { + MutableRefMetadata m = context.createMetadata(MutableRefMetadata.class); + m.setComponentId(value); + return m; + } + + private static IdRefMetadata createIdRef(ParserContext context, String value) { + MutableIdRefMetadata m = context.createMetadata(MutableIdRefMetadata.class); + m.setComponentId(value); + return m; + } + + private static CollectionMetadata createList(ParserContext context, List list) { + MutableCollectionMetadata m = context.createMetadata(MutableCollectionMetadata.class); + m.setCollectionClass(List.class); + m.setValueType(String.class.getName()); + for (String v : list) { + m.addValue(createValue(context, v, String.class.getName())); + } + return m; + } + + private static String getTextValue(Element element) { + StringBuffer value = new StringBuffer(); + NodeList nl = element.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node item = nl.item(i); + if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) { + value.append(item.getNodeValue()); + } + } + return value.toString(); + } + + private static boolean nodeNameEquals(Node node, String name) { + return (name.equals(node.getNodeName()) || name.equals(node.getLocalName())); + } + + public static boolean isBlueprintNamespace(String ns) { + return BLUEPRINT_NAMESPACE.equals(ns); + } + + public String getId(ParserContext context, Element element) { + if (element.hasAttribute(ID_ATTRIBUTE)) { + return element.getAttribute(ID_ATTRIBUTE); + } else { + return generateId(context); + } + } + + public void generateIdIfNeeded(ParserContext context, MutableComponentMetadata metadata) { + if (metadata.getId() == null) { + metadata.setId(generateId(context)); + } + } + + private String generateId(ParserContext context) { + String id; + do { + id = ".cm-" + ++idCounter; + } while (context.getComponentDefinitionRegistry().containsComponentDefinition(id)); + return id; + } + + private Parser getParser(ParserContext ctx) { + if (ctx instanceof ParserContextImpl) { + return ((ParserContextImpl) ctx).getParser(); + } + throw new RuntimeException("Unable to get parser"); + } + + private String extractSystemPropertiesAttribute(Element element) { + String systemProperties = null; + + if (element.hasAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, ExtNamespaceHandler.SYSTEM_PROPERTIES_ATTRIBUTE)) { + systemProperties = element.getAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_0, ExtNamespaceHandler.SYSTEM_PROPERTIES_ATTRIBUTE); + } + + //else if (element.hasAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, ExtNamespaceHandler.SYSTEM_PROPERTIES_ATTRIBUTE)) { + // systemProperties = element.getAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, + // ExtNamespaceHandler.SYSTEM_PROPERTIES_ATTRIBUTE); + //} + return systemProperties; + } + + private String extractIgnoreMissingLocations(Element element) { + String ignoreMissingLocations = null; + if (element.hasAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, ExtNamespaceHandler.IGNORE_MISSING_LOCATIONS_ATTRIBUTE)) { + ignoreMissingLocations = element.getAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_0, + ExtNamespaceHandler.IGNORE_MISSING_LOCATIONS_ATTRIBUTE); + } + //else if (element.hasAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, ExtNamespaceHandler.IGNORE_MISSING_LOCATIONS_ATTRIBUTE)) { + // ignoreMissingLocations = element.getAttributeNS(ExtNamespaceHandler.BLUEPRINT_EXT_NAMESPACE_V1_1, + // ExtNamespaceHandler.IGNORE_MISSING_LOCATIONS_ATTRIBUTE); + //} + return ignoreMissingLocations; + } + + private Metadata parseDefaultProperties(ParserContext context, MutableBeanMetadata enclosingComponent, Element element) { + MutableMapMetadata props = context.createMetadata(MutableMapMetadata.class); + NodeList nl = element.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + Element e = (Element) node; + if (BLUEPRINT_CM_NAMESPACE_1_0.equals(e.getNamespaceURI()) || BLUEPRINT_CM_NAMESPACE_1_1.equals(e.getNamespaceURI())) { + if (nodeNameEquals(e, PROPERTY_ELEMENT)) { + BeanProperty prop = context.parseElement(BeanProperty.class, enclosingComponent, e); + props.addEntry(createValue(context, prop.getName(), String.class.getName()), prop.getValue()); + } + } + } + } + return props; + } +} Property changes on: src/main/java/org/apache/karaf/jaas/cmencrypt/CmEncNamespaceHandler.java ___________________________________________________________________ Added: svn:executable + * Index: src/main/java/org/apache/karaf/jaas/cmencrypt/EncryptedPropertyPlaceholder.java =================================================================== --- src/main/java/org/apache/karaf/jaas/cmencrypt/EncryptedPropertyPlaceholder.java (revision 0) +++ src/main/java/org/apache/karaf/jaas/cmencrypt/EncryptedPropertyPlaceholder.java (revision 0) @@ -0,0 +1,190 @@ +/** + * 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.karaf.jaas.cmencrypt; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Properties; + +import org.apache.aries.blueprint.ExtendedBlueprintContainer; +import org.apache.aries.blueprint.compendium.cm.CmUtils; +import org.apache.aries.blueprint.compendium.cm.ManagedObject; +import org.apache.aries.blueprint.compendium.cm.ManagedObjectManager; +import org.apache.aries.blueprint.ext.PropertyPlaceholder; + +import org.jasypt.encryption.StringEncryptor; +import org.jasypt.properties.PropertyValueEncryptionUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EncryptedPropertyPlaceholder extends PropertyPlaceholder implements ManagedObject { + + private Logger LOGGER = LoggerFactory.getLogger(EncryptedPropertyPlaceholder.class); + private ExtendedBlueprintContainer blueprintContainer; + private ConfigurationAdmin configAdmin; + private String persistentId; + private String updateStrategy; + private ManagedObjectManager managedObjectManager; + private Dictionary properties; + + // Jasypt stuff + private final StringEncryptor stringEncryptor; + + public EncryptedPropertyPlaceholder(final StringEncryptor stringEncryptor) { + // CommonUtils.validateNotNull(stringEncryptor, + // "Encryptor cannot be null"); + this.stringEncryptor = stringEncryptor; + } + + public ExtendedBlueprintContainer getBlueprintContainer() { + return blueprintContainer; + } + + public void setBlueprintContainer(ExtendedBlueprintContainer blueprintContainer) { + this.blueprintContainer = blueprintContainer; + } + + public ConfigurationAdmin getConfigAdmin() { + return configAdmin; + } + + public void setConfigAdmin(ConfigurationAdmin configAdmin) { + this.configAdmin = configAdmin; + } + + public String getPersistentId() { + return persistentId; + } + + public void setPersistentId(String persistentId) { + this.persistentId = persistentId; + } + + public String getUpdateStrategy() { + return updateStrategy; + } + + public void setUpdateStrategy(String updateStrategy) { + this.updateStrategy = updateStrategy; + } + + public ManagedObjectManager getManagedObjectManager() { + return managedObjectManager; + } + + public void setManagedObjectManager(ManagedObjectManager managedObjectManager) { + this.managedObjectManager = managedObjectManager; + } + + public void init() throws Exception { + LOGGER.debug("Initializing EncryptedPropertyPlaceholder"); + Configuration config = CmUtils.getConfiguration(configAdmin, persistentId); + if (config != null) { + properties = config.getProperties(); + } + LOGGER.debug("Props is " + properties); + Properties props = new Properties(); + props.put(Constants.SERVICE_PID, persistentId); + Bundle bundle = blueprintContainer.getBundleContext().getBundle(); + props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName()); + props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION)); + managedObjectManager.register(this, props); + } + + public void destroy() { + LOGGER.debug("Destroying CmPropertyPlaceholder"); + managedObjectManager.unregister(this); + } + + protected String getProperty(String val) { + LOGGER.debug("Retrieving property value {} from configuration with pid {}", val, persistentId); + Object v = null; + if (properties != null) { + v = properties.get(val); + if (v != null) { + LOGGER.debug("Found property value {}", v); + } else { + LOGGER.debug("Property not found in configuration"); + } + } + if (v == null) { + v = super.getProperty(val); + } + + if (!PropertyValueEncryptionUtils.isEncryptedValue(v.toString())) { + return v.toString(); + } + if (this.stringEncryptor != null) { + LOGGER.debug("Decrypting with {}", stringEncryptor); + String value = null; + try { + value = PropertyValueEncryptionUtils.decrypt(v.toString(), this.stringEncryptor); + } catch (Exception e) { + LOGGER.error("Invalid encrypted string " + v.toString()); + value = "Invalid encrypted String " + v.toString(); + } + return value; + } + + return null; + } + + public Bundle getBundle() { + return blueprintContainer.getBundleContext().getBundle(); + } + + public void updated(Dictionary props) { + if ("reload".equalsIgnoreCase(updateStrategy) && !equals(properties, props)) { + LOGGER.debug("Configuration updated for pid={}", persistentId); + // Run in a separate thread to avoid re-entrance + new Thread() { + public void run() { + blueprintContainer.reload(); + } + }.start(); + } + } + + private boolean equals(Dictionary d1, Dictionary d2) { + if (d1 == null || d1.isEmpty()) { + return d2 == null || d2.isEmpty(); + } else if (d2 == null || d1.size() != d2.size()) { + return false; + } else { + for (Enumeration e = d1.keys(); e.hasMoreElements(); ) { + T k = e.nextElement(); + U v1 = d1.get(k); + U v2 = d2.get(k); + if (v1 == null) { + if (v2 != null) { + return false; + } + } else { + if (!v1.equals(v2)) { + return false; + } + } + } + return true; + } + } +} Property changes on: src/main/java/org/apache/karaf/jaas/cmencrypt/EncryptedPropertyPlaceholder.java ___________________________________________________________________ Added: svn:executable + * Index: src/main/resources/org/apache/karaf/jaas/cmencrypt/cmencrypt.xsd =================================================================== --- src/main/resources/org/apache/karaf/jaas/cmencrypt/cmencrypt.xsd (revision 0) +++ src/main/resources/org/apache/karaf/jaas/cmencrypt/cmencrypt.xsd (revision 0) @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: src/main/resources/OSGI-INF/blueprint/cmencrypt.xml =================================================================== --- src/main/resources/OSGI-INF/blueprint/cmencrypt.xml (revision 0) +++ src/main/resources/OSGI-INF/blueprint/cmencrypt.xml (revision 0) @@ -0,0 +1,14 @@ + + + + + http://aries.apache.org/blueprint/xmlns/blueprint-cmenc/v1.1.0 + + + + + + + + + \ No newline at end of file Index: pom.xml =================================================================== --- pom.xml (revision 1297653) +++ pom.xml (working copy) @@ -71,6 +71,12 @@ provided + + org.apache.aries.blueprint + org.apache.aries.blueprint.cm + provided + + org.osgi @@ -85,7 +91,18 @@ test + + org.osgi + org.osgi.core + + + + org.osgi + org.osgi.compendium + + + com.googlecode.pojosr de.kalpatec.pojosr.framework 0.1.6