http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/metamodel/src/main/java/org/apache/tamaya/metamodel/spi/PropertySourceProviderFactory.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/spi/PropertySourceProviderFactory.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/spi/PropertySourceProviderFactory.java new file mode 100644 index 0000000..717f075 --- /dev/null +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/spi/PropertySourceProviderFactory.java @@ -0,0 +1,50 @@ +/* + * 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.tamaya.metamodel.spi; + +import org.apache.tamaya.metamodel.internal.SourceConfig; +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertySourceProvider; + +import java.util.Map; + + +/** + * {@link PropertySource} and {@link SourceConfig} instances that + * implement configurable are configured with the according configuration + * settings provided in the {@code tamaya-config.xml} meta-configuration. + */ +public interface PropertySourceProviderFactory { + + /** + * Resolve the given expression (without the key part). + * @param config the source configuration text, or null. + * @param extendedConfig any further extended configuration, not null, but may be + * empty. + * @return the property source, or null. + */ + PropertySourceProvider create(String config, Map<String, String> extendedConfig); + + /** + * Get the property source type. The type is used to identify the correct factory instance + * to resolve a configured property source. + * @return the (unique) type key, never null and not empty. + */ + String getType(); +}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/metamodel/src/test/resources/tamaya-config.json ---------------------------------------------------------------------- diff --git a/metamodel/src/test/resources/tamaya-config.json b/metamodel/src/test/resources/tamaya-config.json new file mode 100644 index 0000000..b926c25 --- /dev/null +++ b/metamodel/src/test/resources/tamaya-config.json @@ -0,0 +1,39 @@ +// 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 current 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. +// +{ + "context": { + "stage": "DEV" + }, + "config-sources":{ + "env-properties": { + "enabled": "${stage=TEST || stage=PTA || stage=PROD}", + "ordinal": 200 + }, + "sys-properties": {}, + "file": "./config.json", + "resource": { + "path": "/META-INF/application-config.yml", + "multiple": true + }, + "include":{ + "path": "TEST.properties", + "enabled": "${context.cstage==TEST}" + } + } +} + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/metamodel/src/test/resources/tamaya-config.xml ---------------------------------------------------------------------- diff --git a/metamodel/src/test/resources/tamaya-config.xml b/metamodel/src/test/resources/tamaya-config.xml new file mode 100644 index 0000000..ee36e0e --- /dev/null +++ b/metamodel/src/test/resources/tamaya-config.xml @@ -0,0 +1,62 @@ +<!-- +// 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 current 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. +// --> +<configuration> + <formats> + <format name="yaml"/> + <format name="json"/> + </formats> + <!--<converters>--> + <!--<converter type="AllInOneConverter"/>--> + <!--</converters>--> + <filters> + <filter type="UsageTrackerFilter"/> + </filters> + + <context> + <context-entry name="stage">${properties:system:STAGE?default=DEV}</context-entry> + <context-entry name="app">${properties:system.APP?default=NONE}</context-entry> + <context-entry name="context">${java:org.apache.tamaya.context.Context#id()}</context-entry> + <context-entry name="company">Trivadis</context-entry> + </context> + + <sources> + <source enabled="${stage=TEST || stage=PTA || stage=PROD}" + uri="properties:environment"> + <decorator name="maped-to">ENV.</decorator> + <decorator name="secured"> + <param name="roles">admin,power-user</param> + <param name="policy">mask</param> + </decorator> + </source> + <source uri="properties:system"/> + <source name="FILE:config.json" observe="true" observe-period="20000" + uri="file:/./config.json" /> + <source name="CP:config.yml" uri="classpath*://META-INF/application-config.yml"/> + <source name="MINE" uri="propertysource:ch.mypack.MyClassSource"> + <param name="locale">de</param> + </source> + <include enabled="${context.cstage==TEST}">TEST-config.xml</include> + <source name="CONFIG-DIR" uri="dir:/${CONFIG-DIR}/**/*.json"/> + <source name="SERVER" uri="https://www.confdrive.com/cfg/customerId=${}"> + <param name="locale">de</param> + </source> + </sources> + +</configuration> + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/metamodel/src/test/resources/tamaya-config.yaml ---------------------------------------------------------------------- diff --git a/metamodel/src/test/resources/tamaya-config.yaml b/metamodel/src/test/resources/tamaya-config.yaml new file mode 100644 index 0000000..4851c97 --- /dev/null +++ b/metamodel/src/test/resources/tamaya-config.yaml @@ -0,0 +1,87 @@ +# +# 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 current 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. +# +<!-- +// 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 current 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. +// --> +------ +configuration: + formats: + - format: yaml + - format: json + converters: + - converter: AllInOneConverter + filters: + - filter: UsageTrackerFilter + + context: + - stage: ${properties:system:STAGE?default=DEV} + - app: ${properties:system.APP?default=NONE} + - context: ${java:org.apache.tamaya.context.Context#id()} + - company: Trivadis + + sources: + - ENVIRONMENT: + uri: properties:environment + enabled: ${stage=TEST || stage=PTA || stage=PROD} + decorator: + - maped-to: ENV. + - secured: + roles: admin,power-user + policy: mask + - SYSPROPS: + uri: properties:system + - FILE:config.json: + uri: file:/./config.json + name: FILE:config.json + observe: true + observe-period: 20000 + - CP:config.yml: + uri: classpath*://META-INF/application-config.yml + - MINE: + uri: propertysource:ch.mypack.MyClassSource + locale=de + - <include>: + resource: TEST-config.xml + enabled: ${context.cstage==TEST} + - CONFIG-DIR: + uri: dir:/${CONFIG-DIR}/**/*.json + - SERVER: + uri: https://www.confdrive.com/cfg/customerId=${} + params: + - locale: de + + + + + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/pom.xml ---------------------------------------------------------------------- diff --git a/osgi/pom.xml b/osgi/pom.xml new file mode 100644 index 0000000..9d3682a --- /dev/null +++ b/osgi/pom.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <!-- + + 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. + --> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-sandbox</artifactId> + <version>0.3-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-osgi</artifactId> + <packaging>bundle</packaging> + <name>Apache Tamaya Integration - OSGi Services :: Tamaya Config</name> + <description>Tamaya Based OSGI Implementation of ConfigAdmin and Config Injection</description> + + <properties> + <felix.plugin.version>2.5.4</felix.plugin.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>${felix.plugin.version}</version> + <inherited>true</inherited> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-Activator> + org.apache.tamaya.integration.osgi.Activator + </Bundle-Activator> + <Export-Service> + org.osgi.service.cm.ConfigurationAdmin + </Export-Service> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.configadmin</artifactId> + <version>1.8.8</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>6.0.0</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-spisupport</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-injection</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/main/java/org/apache/tamaya/integration/osgi/Activator.java ---------------------------------------------------------------------- diff --git a/osgi/src/main/java/org/apache/tamaya/integration/osgi/Activator.java b/osgi/src/main/java/org/apache/tamaya/integration/osgi/Activator.java new file mode 100644 index 0000000..7425dfb --- /dev/null +++ b/osgi/src/main/java/org/apache/tamaya/integration/osgi/Activator.java @@ -0,0 +1,134 @@ +/* + * 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.tamaya.integration.osgi; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.inject.ConfigurationInjection; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.util.tracker.ServiceTracker; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Activator that registers the Tamaya based Service Class for {@link ConfigurationAdmin}, + * using a default service priority of {@code 0}. This behaviour is configurable based on OSGI properties: + * <ul> + * <li><p><b>org.tamaya.integration.osgi.cm.ranking, type: int</b> allows to configure the OSGI service ranking for + * Tamaya based ConfigurationAdmin instance. The default ranking used is 10.</p></li> + * <li><p><b>org.tamaya.integration.osgi.cm.override, type: boolean</b> allows to configure if Tamaya should + * register its ConfigAdmin service. Default is true.</p></li> + * </ul> + */ +public class Activator implements BundleActivator { + + private static final String SERVICE_RANKING_PROP = "org.tamaya.integration.osgi.cm.ranking"; + + private static final String SERVICE_OVERRIDE_PROP = "org.tamaya.integration.osgi.cm.override"; + + private static final String SERVICE_INJECT_PROP = "org.tamaya.integration.osgi.cm.inject"; + + private static final Integer DEFAULT_RANKING = 10; + + private static final Logger LOG = Logger.getLogger(Activator.class.getName()); + + private ServiceRegistration<ConfigurationAdmin> registration; + + private ServiceTracker<Object, Object> injectionTracker; + + @Override + public void start(BundleContext context) throws Exception { + String val = context.getProperty(SERVICE_OVERRIDE_PROP); + if(val == null || Boolean.parseBoolean(val)){ + Dictionary<String, Object> props = new Hashtable<>(); + String ranking = context.getProperty(SERVICE_RANKING_PROP); + if (ranking == null) { + props.put(Constants.SERVICE_RANKING, DEFAULT_RANKING); + } else { + props.put(Constants.SERVICE_RANKING, Integer.valueOf(ranking)); + } + TamayaConfigAdminImpl cm = new TamayaConfigAdminImpl(context); + registration = context.registerService(ConfigurationAdmin.class, cm, props); + } + + // register injection mechanisms, if not configured otherwise + val = context.getProperty(SERVICE_INJECT_PROP); + if(val == null || Boolean.parseBoolean(val)){ + injectionTracker = new ServiceTracker<Object, Object>(context, Object.class, null) { + @Override + public Object addingService(ServiceReference<Object> reference) { + Object service = context.getService(reference); + Object pidObj = reference.getProperty(Constants.SERVICE_PID); + if (pidObj instanceof String) { + String pid = (String) pidObj; + ConfigurationAdmin configAdmin = null; + ServiceReference<ConfigurationAdmin> adminRef = + context.getServiceReference(ConfigurationAdmin.class); + if(adminRef!=null){ + configAdmin = context.getService(adminRef); + } + try { + Configuration targetConfig = null; + if(configAdmin != null){ + org.osgi.service.cm.Configuration osgiConfig = configAdmin.getConfiguration(pid); + if(osgiConfig!=null){ + targetConfig = new OSGIEnhancedConfiguration(osgiConfig); + } + } + if(targetConfig==null){ + targetConfig = ConfigurationProvider.getConfiguration(); + } + ConfigurationInjection.getConfigurationInjector().configure(service, targetConfig); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error configuring Service: " + service, e); + } + } else { + LOG.log(Level.SEVERE, "Unsupported pid: " + pidObj); + } + return service; + } + + @Override + public void removedService(ServiceReference<Object> reference, Object service) { + context.ungetService(reference); + } + }; + injectionTracker.open(); + } + } + + @Override + public void stop(BundleContext context) throws Exception { + if (registration != null) { + registration.unregister(); + } + if(injectionTracker!=null){ + injectionTracker.close(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIConfigRootMapper.java ---------------------------------------------------------------------- diff --git a/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIConfigRootMapper.java b/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIConfigRootMapper.java new file mode 100644 index 0000000..836df8b --- /dev/null +++ b/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIConfigRootMapper.java @@ -0,0 +1,36 @@ +/* + * 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.tamaya.integration.osgi; + +/** + * Mapping function for mapping Tamaya configuration sections to OSGI pids. + */ +public interface OSGIConfigRootMapper { + + /** + * Map the given OSGI pid to a corresponding configuration section in Tamaya. Es an example (and this is also the + * default implemented) a configuration mapping for {@code pid/factoryPid==myBundle} could be {@code [bundle:myBundle]}. + * This mapping is used as a prefix when collecting the corresponding entries for the OSGI configuration. + * @param pid the OSGI pid, or null + * @param factoryPid the OSGI factoryPid, or null + * @return return the corresponding config root section. For ommitting any root section simply return an empty + * String. + */ + String getTamayaConfigRoot(String pid, String factoryPid); +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIEnhancedConfiguration.java ---------------------------------------------------------------------- diff --git a/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIEnhancedConfiguration.java b/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIEnhancedConfiguration.java new file mode 100644 index 0000000..5e813af --- /dev/null +++ b/osgi/src/main/java/org/apache/tamaya/integration/osgi/OSGIEnhancedConfiguration.java @@ -0,0 +1,117 @@ +/* + * 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.tamaya.integration.osgi; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spisupport.BasePropertySource; +import org.apache.tamaya.spisupport.DefaultConfiguration; +import org.apache.tamaya.spisupport.DefaultConfigurationContext; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Configuration object that also reflects the values provided by the OSGI ConfigAdmin Configuration. + * Similar to other tamaya areas adding a tamaya.ordinal into the corresponding OSGI configuration for + * a pif/factoryPid allows to control the ordinal/priority of the OSGI configuration related to other + * configured Tamaya Property Sources. Overall the configuration evaluation for Tamaya follows the + * same rules, with the difference that each bunldle owns its own ConfigAdmin based part. From + * Tamaya, the granularity depends on the implementation of the ConfigurationProviderSpi. By default + * Tamaya configuration is managed as a global resource/config tree, wheres bundle specific sections are + * selected only. + */ +public class OSGIEnhancedConfiguration extends DefaultConfiguration{ + /** The default ordinal used for the OSGI config, */ + private static final int OSGI_DEFAULT_ORDINAL = 0; + + /** + * Constructor. + * + * @param osgiConfiguration The OSGI configuration found. + */ + public OSGIEnhancedConfiguration(org.osgi.service.cm.Configuration osgiConfiguration) { + super(new OSGIConfigurationContext(osgiConfiguration)); + } + + /** + * Class that models a Tamaya ConfigurationContext, which implicitly contains the bundle specific + * Configuration wrapped into a Tamaya PropertySource. + */ + private static final class OSGIConfigurationContext extends DefaultConfigurationContext{ + private OSGIPropertySource osgiPropertySource; + + public OSGIConfigurationContext(org.osgi.service.cm.Configuration osgiConfiguration){ + if(osgiConfiguration!=null) { + this.osgiPropertySource = new OSGIPropertySource(osgiConfiguration); + } + } + + @Override + public List<PropertySource> getPropertySources() { + List<PropertySource> sources = super.getPropertySources(); + if(osgiPropertySource!=null){ + sources.add(osgiPropertySource); + } + return sources; + } + } + + /** + * Tamaya PropertySource providing the values from an OSGI Configuration. + */ + private static final class OSGIPropertySource extends BasePropertySource{ + + private final org.osgi.service.cm.Configuration osgiConfiguration; + + public OSGIPropertySource(org.osgi.service.cm.Configuration osgiConfiguration){ + this.osgiConfiguration = Objects.requireNonNull(osgiConfiguration); + } + + @Override + public int getDefaultOrdinal() { + String val = System.getProperty("osgi.defaultOrdinal"); + if(val!=null){ + return Integer.parseInt(val.trim()); + } + return OSGI_DEFAULT_ORDINAL; + } + + @Override + public String getName() { + return "OSGIConfig:pid="+ + (osgiConfiguration.getPid()!=null?osgiConfiguration.getPid():osgiConfiguration.getFactoryPid()); + } + + @Override + public Map<String, String> getProperties() { + Map<String, String> map = new HashMap<>(); + Dictionary<String,Object> dict = osgiConfiguration.getProperties(); + Enumeration<String> keys = dict.keys(); + while(keys.hasMoreElements()){ + String key = keys.nextElement(); + map.put(key,String.valueOf(dict.get(key))); + } + return map; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigAdminImpl.java ---------------------------------------------------------------------- diff --git a/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigAdminImpl.java b/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigAdminImpl.java new file mode 100644 index 0000000..7bf4da7 --- /dev/null +++ b/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigAdminImpl.java @@ -0,0 +1,196 @@ +/* + * 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.tamaya.integration.osgi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceFactory; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.cm.ManagedServiceFactory; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Tamaya based implementation of an OSGI {@link ConfigurationAdmin}. + */ +public class TamayaConfigAdminImpl implements ConfigurationAdmin { + /** the logger. */ + private static final Logger LOG = Logger.getLogger(TamayaConfigAdminImpl.class.getName()); + + /** The OSGI context. */ + private final BundleContext context; + /** THe optional OSGI parent service. */ + private ConfigurationAdmin parent; + /** The cached configurations. */ + private Map<String,Configuration> configs = new ConcurrentHashMap<>(); + /** The configuration section mapper. */ + private OSGIConfigRootMapper configRootMapper; + + /** + * Create a new config. + * @param context the OSGI context + */ + TamayaConfigAdminImpl(BundleContext context) { + this.context = context; + this.configRootMapper = loadConfigRootMapper(); + ServiceReference<ConfigurationAdmin> ref = context.getServiceReference(ConfigurationAdmin.class); + this.parent = ref!=null?context.getService(ref):null; + ServiceTracker<ManagedService, ManagedService> serviceTracker = new ServiceTracker<ManagedService, + ManagedService>(context, ManagedService.class, null) { + @Override + public ManagedService addingService(ServiceReference<ManagedService> reference) { + ManagedService service = context.getService(reference); + Object pidObj = reference.getProperty(Constants.SERVICE_PID); + if (pidObj instanceof String) { + String pid = (String) pidObj; + try { + Configuration config = getConfiguration(pid); + if(config==null){ + service.updated(null); + } else{ + service.updated(config.getProperties()); + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Error configuring ManagedService: " + service, e); + } + } else { + LOG.log(Level.SEVERE, "Unsupported pid: " + pidObj); + } + return service; + } + + @Override + public void removedService(ServiceReference<ManagedService> reference, ManagedService service) { + context.ungetService(reference); + } + }; + serviceTracker.open(); + + ServiceTracker<ServiceFactory, ServiceFactory> factoryTracker + = new ServiceTracker<ServiceFactory, ServiceFactory>(context, ServiceFactory.class, null) { + @Override + public ServiceFactory addingService(ServiceReference<ServiceFactory> reference) { + ServiceFactory factory = context.getService(reference); + if(factory instanceof ManagedServiceFactory) { + Object pidObj = reference.getProperty(Constants.SERVICE_PID); + if (pidObj instanceof String) { + String pid = (String) pidObj; + try { + Configuration config = getConfiguration(pid); + if (config != null) { + ((ManagedServiceFactory) factory).updated(config.getFactoryPid(), config.getProperties()); + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Error configuring ManagedServiceFactory: " + factory, e); + } + } else { + LOG.log(Level.SEVERE, "Unsupported pid: " + pidObj); + } + } + return factory; + } + + @Override + public void removedService(ServiceReference<ServiceFactory> reference, ServiceFactory service) { + super.removedService(reference, service); + } + }; + factoryTracker.open(); + } + + @Override + public Configuration createFactoryConfiguration(String factoryPid) throws IOException { + return createFactoryConfiguration(factoryPid, null); + } + + @Override + public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException { + return new TamayaConfigurationImpl(factoryPid, null, configRootMapper, this.parent); + } + + @Override + public Configuration getConfiguration(String pid, String location) throws IOException { + return getConfiguration(pid); + } + + @Override + public Configuration getConfiguration(String pid) throws IOException { + return new TamayaConfigurationImpl(pid, null, configRootMapper, this.parent); + } + + @Override + public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException { + Collection<Configuration> result; + if (filter == null) { + result = this.configs.values(); + } else { + result = new ArrayList<>(); + Filter flt = context.createFilter(filter); + for (Configuration config : this.configs.values()) { + if (flt.match(config.getProperties())) { + result.add(config); + } + } + } + return result.isEmpty() ? null : result.toArray(new Configuration[configs.size()]); + } + + /** + * Loads the configuration toor mapper using the OSGIConfigRootMapper OSGI service resolving mechanism. If no + * such service is available it loads the default mapper. + * @return the mapper to be used, bever null. + */ + private OSGIConfigRootMapper loadConfigRootMapper() { + OSGIConfigRootMapper mapper = null; + ServiceReference<OSGIConfigRootMapper> ref = context.getServiceReference(OSGIConfigRootMapper.class); + if(ref!=null){ + mapper = context.getService(ref); + } + if(mapper==null){ + mapper = new OSGIConfigRootMapper() { + @Override + public String getTamayaConfigRoot(String pid, String factoryPid) { + if(pid!=null) { + return "[bundle:" + pid +']'; + } else{ + return "[bundle:" + factoryPid +']'; + } + } + @Override + public String toString(){ + return "Default OSGIConfigRootMapper(pid -> [bundle:pid], factoryPid -> [bundle:factoryPid]"; + } + }; + } + return mapper; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigurationImpl.java ---------------------------------------------------------------------- diff --git a/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigurationImpl.java b/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigurationImpl.java new file mode 100644 index 0000000..c7b0864 --- /dev/null +++ b/osgi/src/main/java/org/apache/tamaya/integration/osgi/TamayaConfigurationImpl.java @@ -0,0 +1,127 @@ +/* + * 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.tamaya.integration.osgi; + +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.functions.PropertyMatcher; +import org.apache.tamaya.functions.ConfigurationFunctions; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; + +/** + * Tamaya based implementation of an OSGI {@link Configuration}. + */ +public class TamayaConfigurationImpl implements Configuration { + private static final Logger LOG = Logger.getLogger(TamayaConfigurationImpl.class.getName()); + private final String pid; + private final String factoryPid; + private Map<String, String> properties = new HashMap<>(); + private org.apache.tamaya.Configuration config; + + /** + * Constructor. + * @param confPid the OSGI pid + * @param factoryPid the factory pid + * @param configRootMapper the mapper that maps the pids to a tamaya root section. + * @param parent the (optional delegating parent, used as default). + */ + TamayaConfigurationImpl(String confPid, String factoryPid, OSGIConfigRootMapper configRootMapper, + ConfigurationAdmin parent) { + this.pid = confPid; + this.factoryPid = factoryPid; + if(parent!=null){ + try { + Dictionary<String, Object> conf = parent.getConfiguration(confPid, factoryPid).getProperties(); + if(conf!=null) { + LOG.info("Configuration: Adding default parameters from parent: " + parent.getClass().getName()); + Enumeration<String> keys = conf.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + this.properties.put(key, conf.get(key).toString()); + } + } + } catch (IOException e) { + LOG.log(Level.WARNING, "Error reading parent OSGI config.", e); + } + } + this.config = ConfigurationProvider.getConfiguration(); + final String rootKey = configRootMapper.getTamayaConfigRoot(pid, factoryPid); + LOG.info("Configuration: Evaluating Tamaya configuration for '" + rootKey + "'."); + this.properties.putAll( + config.with(ConfigurationFunctions.section(rootKey, true)).getProperties()); + } + + @Override + public String getPid() { + return pid; + } + + @Override + public Dictionary<String, Object> getProperties() { + return new Hashtable<String, Object>(properties); + } + + @Override + public void update(Dictionary<String, ?> properties) throws IOException { + throw new UnsupportedOperationException("Nuatability not yet supported."); + // ConfigChangeProvider.createChangeRequest(this.config) + } + + @Override + public void delete() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public String getFactoryPid() { + return factoryPid; + } + + @Override + public void update() throws IOException { + this.config = ConfigurationProvider.getConfiguration(); + this.properties = config.with(ConfigurationFunctions.filter(new PropertyMatcher() { + @Override + public boolean test(String key, String value) { +// TODO define name space / SPI + return false; + } + })).getProperties(); + } + + @Override + public void setBundleLocation(String location) { + } + + @Override + public String getBundleLocation() { + return null; + } + + @Override + public long getChangeCount() { + return 0; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/test/resources/META-INF/javaconfiguration.properties ---------------------------------------------------------------------- diff --git a/osgi/src/test/resources/META-INF/javaconfiguration.properties b/osgi/src/test/resources/META-INF/javaconfiguration.properties new file mode 100644 index 0000000..0f09ce9 --- /dev/null +++ b/osgi/src/test/resources/META-INF/javaconfiguration.properties @@ -0,0 +1,18 @@ +# 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. +# +[bundle:tamaya]my.testProperty=success! \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/test/resources/arquillian.xml ---------------------------------------------------------------------- diff --git a/osgi/src/test/resources/arquillian.xml b/osgi/src/test/resources/arquillian.xml new file mode 100644 index 0000000..a4c885a --- /dev/null +++ b/osgi/src/test/resources/arquillian.xml @@ -0,0 +1,27 @@ +<!-- +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 current 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. +--> +<arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> + + <container qualifier="osgi" default="true"> + <configuration> + <property name="frameworkProperties">src/test/resources/felix.properties</property> + </configuration> + </container> +</arquillian> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/osgi/src/test/resources/felix.properties ---------------------------------------------------------------------- diff --git a/osgi/src/test/resources/felix.properties b/osgi/src/test/resources/felix.properties new file mode 100644 index 0000000..de50401 --- /dev/null +++ b/osgi/src/test/resources/felix.properties @@ -0,0 +1,23 @@ +# 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. +# +# org.osgi.service.cm;org.apache.felix.cm;org.apache.tamaya;org.apache.tamaya.spi; +org.osgi.framework.bootdelegation=org.apache.tamaya,org.apache.tamaya.integration.osgi,org.apache.tamaya.integration.osgi.test +felix.log.level=4 #debug logging +# felix.auto.deploy.dir=../test-bundles +org.osgi.framework.storage=target/felix-cache +felix.fileinstall.dir=./test-bundles \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/propertysources/src/test/java/org/apache/tamaya/propertysources/MetainfConfigPropertySourceProviderTest.java ---------------------------------------------------------------------- diff --git a/propertysources/src/test/java/org/apache/tamaya/propertysources/MetainfConfigPropertySourceProviderTest.java b/propertysources/src/test/java/org/apache/tamaya/propertysources/MetainfConfigPropertySourceProviderTest.java new file mode 100644 index 0000000..0250ff6 --- /dev/null +++ b/propertysources/src/test/java/org/apache/tamaya/propertysources/MetainfConfigPropertySourceProviderTest.java @@ -0,0 +1,37 @@ +/* + * 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.tamaya.propertysources; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Created by atsticks on 30.10.16. + */ +public class MetainfConfigPropertySourceProviderTest { + + @Test + public void getPropertySources_Default() throws Exception { + MetainfConfigPropertySourceProvider provider = new MetainfConfigPropertySourceProvider(); + assertNotNull(provider.getPropertySources()); + // TODO add test for containing property sources. + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/pom.xml ---------------------------------------------------------------------- diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..d2abf18 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,203 @@ +<!-- +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 current 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-sandbox</artifactId> + <version>0.3-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-server</artifactId> + <name>Apache Tamaya Modules - Server Extension</name> + <packaging>bundle</packaging> + + <properties> + <jdkVersion>1.7</jdkVersion> + <tomcat.version>7.0.57</tomcat.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + <version>${tomcat.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-jasper</artifactId> + <version>${tomcat.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-juli</artifactId> + <version>${tomcat.version}</version> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-jaxrs_2.0_spec</artifactId> + <version>1.0-alpha-1</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-frontend-jaxrs</artifactId> + <version>3.1.6</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>tamaya-json</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>${project.basedir}/src/main/resources</directory> + <filtering>true</filtering> + <includes> + <include>META-INF/server-version.properties</include> + </includes> + </resource> + <resource> + <directory>${project.basedir}/src/main/resources</directory> + <filtering>false</filtering> + <excludes> + <exclude>META-INF/server-version.properties</exclude> + </excludes> + </resource> + + </resources> + <plugins> + <plugin> + <groupId>com.spotify</groupId> + <artifactId>docker-maven-plugin</artifactId> + <version>0.3.258</version> + <configuration> + <imageName>apache/tamaya/config-server</imageName> + <imageTags> + <imageTag>${project.version}</imageTag> + </imageTags> + <baseImage>java:8-jre</baseImage> + <entryPoint>["java", "-jar", "/${project.build.finalName}.jar", "server", "/config-server.yml"]</entryPoint> + <exposes> + <expose>8080</expose> + </exposes> + <resources> + <resource> + <targetPath>/</targetPath> + <directory>${project.build.directory}</directory> + <include>${project.build.finalName}.jar</include> + </resource> + <resource> + <targetPath>/</targetPath> + <directory>${project.build.directory}/classes</directory> + <include>config-server.yml</include> + </resource> + </resources> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> + </manifest> + </archive> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <createDependencyReducedPom>true</createDependencyReducedPom> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>org.apache.tamaya.server.ConfigServiceApp</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package> + org.apache.tamaya.server, + org.apache.tamaya.server.spi + </Export-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/java/org/apache/tamaya/server/ConfigServiceApp.java ---------------------------------------------------------------------- diff --git a/server/src/main/java/org/apache/tamaya/server/ConfigServiceApp.java b/server/src/main/java/org/apache/tamaya/server/ConfigServiceApp.java new file mode 100644 index 0000000..1db06f3 --- /dev/null +++ b/server/src/main/java/org/apache/tamaya/server/ConfigServiceApp.java @@ -0,0 +1,77 @@ +/* + * 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.tamaya.server; + +import org.apache.catalina.Context; +import org.apache.catalina.Wrapper; +import org.apache.catalina.startup.Tomcat; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; + +import javax.ws.rs.core.Application; +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +/** + * Main Application for the Tamaya Configuration Server. + */ +public class ConfigServiceApp { + + /** + * Utility class. + */ + private ConfigServiceApp(){} + + /** + * JAX RS Application. + */ + public static class ResourceLoader extends Application{ + + @Override + public Set<Class<?>> getClasses() { + final Set<Class<?>> classes = new HashSet<>(); + // register root resource + classes.add(ConfigurationResource.class); + return classes; + } + } + + public static void main(String... args) throws Exception { + Configuration config = ConfigurationProvider.getConfiguration(); + String contextPath = config.getOrDefault("tamaya.server.contextPath", "/"); + String appBase = "."; + Tomcat tomcat = new Tomcat(); + tomcat.setPort(config.getOrDefault("tamaya.server.port", Integer.class, 8085)); + + // Define a web application context. + Context context = tomcat.addWebapp(contextPath, new File( + appBase).getAbsolutePath()); + // Add servlet that will register Jersey REST resources + String servletName = "cxf-servlet"; + Wrapper wrapper = tomcat.addServlet(context, servletName, + org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet.class.getName()); + wrapper.addInitParameter("javax.ws.rs.Application", ResourceLoader.class.getName()); + context.addServletMapping("/*", servletName); + tomcat.start(); + tomcat.getServer().await(); + } + + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/java/org/apache/tamaya/server/ConfigurationResource.java ---------------------------------------------------------------------- diff --git a/server/src/main/java/org/apache/tamaya/server/ConfigurationResource.java b/server/src/main/java/org/apache/tamaya/server/ConfigurationResource.java new file mode 100644 index 0000000..f13446a --- /dev/null +++ b/server/src/main/java/org/apache/tamaya/server/ConfigurationResource.java @@ -0,0 +1,310 @@ +/* + * 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.tamaya.server; + +import java.io.StringWriter; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonWriter; +import javax.json.stream.JsonGenerator; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.functions.ConfigurationFunctions; + +/** + * Configuration resource with an etcd compatible REST API + * (excluding the blocking API calls). + */ +@Path("/") +@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) +public class ConfigurationResource { + private final AtomicLong readCounter = new AtomicLong(); + private final AtomicLong writeCounter = new AtomicLong(); + private final AtomicLong deleteCounter = new AtomicLong(); + + + @GET + @Path("/version") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + public String version() { + String product = VersionProperties.getProduct().replace("\"", "\\\""); + String version = VersionProperties.getVersion().replace("\"", "\\\""); + + return String.format("{ \"version\" : \"%s: %s\" }", product, version); + } + + @GET + @Path("/v2/keys") + public String readEtcdConfig(@QueryParam("recursive") Boolean recursive) { + return readConfig(recursive); + } + + /** + * This models a etcd2 compliant access point for getting a property value. + * + * @param recursive NOT YET IMPLEMENTED! + * @return all configuration property values. + */ + @GET + @Path("/keys") + public String readConfig(@QueryParam("recursive") Boolean recursive) { + readCounter.incrementAndGet(); + final Configuration config = ConfigurationProvider.getConfiguration(); + final Map<String, String> children = config.getProperties(); + final JsonArrayBuilder ab = Json.createArrayBuilder(); + for (final Map.Entry<String, String> en : children.entrySet()) { + final Node node = new Node(config, en.getKey(), "node"); + ab.add(node.createJsonObject()); + } + final Node node = new Node(config, null, "node", ab.build()); + final JsonObjectBuilder root = Json.createObjectBuilder().add("action", "get") + .add("node", node.createJsonObject()); + final StringWriter writer = new StringWriter(); + final JsonWriter jwriter = Json.createWriter(writer); + jwriter.writeObject(root.build()); + return writer.toString(); + } + + /** + * This models a etcd2 compliant access point for getting a property value. + * + * @param key name of the key to show + * @param recursive NOT YET IMPLEMENTED! + * @return specific configuration key derived from the given key name. + */ + @GET + @Path("/v2/keys/{key}") + public String readEtcdConfig(@PathParam("key") String key, @QueryParam("recursive") Boolean recursive) { + return readConfig(key, recursive); + } + + /** + * This models a etcd2 compliant access point for getting a property value. + * + * @param key name of the key to show + * @param recursive NOT YET IMPLEMENTED! + * @return configuration value of the given key. + */ + @GET + @Path("/keys/{key}") + public String readConfig(@PathParam("key") String key, @QueryParam("recursive") Boolean recursive) { + readCounter.incrementAndGet(); + final Configuration config = ConfigurationProvider.getConfiguration(); + if (key != null) { + if (key.startsWith("/")) { + key = key.substring(1); + } + if (config.get(key) != null && !recursive) { + final Node node = new Node(config, key, "node"); + final JsonObjectBuilder root = Json.createObjectBuilder().add("action", "get") + .add("node", node.createJsonObject()); + final StringWriter writer = new StringWriter(); + final JsonGenerator gen = Json.createGenerator(writer); + gen.write(root.build()); + return writer.toString(); + } + } + Map<String, String> children = null; + if (key == null) { + children = config.getProperties(); + } else { + children = config.with(ConfigurationFunctions.section(key)).getProperties(); + } + final JsonArrayBuilder ab = Json.createArrayBuilder(); + for (final Map.Entry<String, String> en : children.entrySet()) { + final Node node = new Node(config, en.getKey(), "node"); + ab.add(node.createJsonObject()); + } + final Node node = new Node(config, key, "node", ab.build()); + final JsonObjectBuilder root = Json.createObjectBuilder().add("action", "get") + .add("node", node.createJsonObject()); + final StringWriter writer = new StringWriter(); + final JsonWriter jwriter = Json.createWriter(writer); + jwriter.writeObject(root.build()); + return writer.toString(); + } + + @PUT + @Path("/v2/keys/{key}") + public String writeEtcdConfig(@PathParam("key") String key, @javax.ws.rs.FormParam("value") String value, + @FormParam("ttl") Integer ttl) { + return writeConfig(key, value, ttl); + } + + /** + * This models a etcd2 compliant access point for setting a property value: + * <pre> + * { + * "action": "set", + * "node": { + * "createdIndex": 3, + * "key": "/message", + * "modifiedIndex": 3, + * "value": "Hello etcd" + * }, + * "prevNode": { + * "createdIndex": 2, + * "key": "/message", + * "value": "Hello world", + * "modifiedIndex": 2 + * } + * } + * </pre> + * + * @param key name of the key to show + * @param value configuration value for the given key + * @param ttl time to live + * @return written configuration value. + */ + @PUT + @Path("/keys/{key}") + public String writeConfig(@PathParam("key") String key, @javax.ws.rs.FormParam("value") String value, + @FormParam("ttl") Integer ttl) { + writeCounter.incrementAndGet(); + final Configuration config = ConfigurationProvider.getConfiguration(); + if (key.startsWith("/")) { + key = key.substring(1); + } + final Node prevNode = new Node(config, key, "prevNode"); + // TODO implement write! value and ttl as input + final Node node = new Node(config, key, "node"); + final JsonObjectBuilder root = Json.createObjectBuilder().add("action", "set") + .add("node", node.createJsonObject()) + .add("prevNode", prevNode.createJsonObject()); + final StringWriter writer = new StringWriter(); + final JsonWriter jwriter = Json.createWriter(writer); + jwriter.writeObject(root.build()); + return writer.toString(); + } + + @DELETE + @Path("/v2/keys/{key}") + public String deleteEtcdConfig(@PathParam("key") String key) { + return deleteConfig(key); + } + + @DELETE + @Path("/keys/{key}") + public String deleteConfig(@PathParam("key") String key) { + deleteCounter.incrementAndGet(); + final Configuration config = ConfigurationProvider.getConfiguration(); + if (key.startsWith("/")) { + key = key.substring(1); + } + final Node prevNode = new Node(config, key, "prevNode"); + // TODO implement write! value and ttl as input + final Node node = new Node(config, key, "node"); + final JsonObjectBuilder root = Json.createObjectBuilder().add("action", "delete") + .add("node", node.createJsonObject()) + .add("prevNode", prevNode.createJsonObject()); + final StringWriter writer = new StringWriter(); + final JsonWriter jwriter = Json.createWriter(writer); + jwriter.writeObject(root.build()); + return writer.toString(); + } + + public long getDeleteCounter() { + return deleteCounter.get(); + } + + public long getReadCounter() { + return readCounter.get(); + } + + public long getWriteCounter() { + return writeCounter.get(); + } + + /** + * Internal representation of a configuration node as modeled by etc. + */ + private static final class Node { + private Integer createdIndex; + private Integer modifiedIndex; + private final String key; + private String value; + private final String nodeId; + private Integer ttl; + private String expiration; + private final JsonArray nodes; + + Node(Configuration config, String key, String nodeId) { + this(config, key, nodeId, null); + } + + Node(Configuration config, String key, String nodeId, JsonArray nodes) { + this.key = key; + this.nodeId = Objects.requireNonNull(nodeId); + if (key != null) { + value = config.get(key); + createdIndex = config.getOrDefault("_" + key + ".createdIndex", Integer.class, null); + modifiedIndex = config.getOrDefault("_" + key + ".modifiedIndex", Integer.class, null); + ttl = config.getOrDefault("_" + key + ".ttl", Integer.class, null); + expiration = config.getOrDefault("_" + key + ".expiration", null); + } + this.nodes = nodes; + } + + JsonObject createJsonObject() { + final JsonObjectBuilder nodeBuilder = Json.createObjectBuilder(); + if (key != null) { + nodeBuilder.add("key", '/' + key); + } else { + nodeBuilder.add("dir", true); + } + if (value != null) { + nodeBuilder.add("value", value); + } + if (createdIndex != null) { + nodeBuilder.add("createdIndex", createdIndex.intValue()); + } + if (modifiedIndex != null) { + nodeBuilder.add("modifiedIndex", modifiedIndex.intValue()); + } + if (ttl != null) { + nodeBuilder.add("ttl", ttl.intValue()); + } + if (expiration != null) { + nodeBuilder.add("expiration", value); + } + if (nodes != null) { + nodeBuilder.add("nodes", nodes); + } + return nodeBuilder.build(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/java/org/apache/tamaya/server/VersionProperties.java ---------------------------------------------------------------------- diff --git a/server/src/main/java/org/apache/tamaya/server/VersionProperties.java b/server/src/main/java/org/apache/tamaya/server/VersionProperties.java new file mode 100644 index 0000000..e271694 --- /dev/null +++ b/server/src/main/java/org/apache/tamaya/server/VersionProperties.java @@ -0,0 +1,68 @@ +/* + * 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.tamaya.server; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * <p>This class gives access to the current name and the current version information + * at runtime.</p> + * + * <p>All information offered by this is loaded from a properties file at + * {@link VersionProperties#VERSION_PROPERTY_FILE}.</p> + */ +public class VersionProperties { + private static final String VERSION_PROPERTY_FILE = "/META-INF/tamaya-server-version.properties"; + + static { + try (InputStream resource = VersionProperties.class.getResourceAsStream(VERSION_PROPERTY_FILE)) { + if (null == resource) { + throw new ExceptionInInitializerError("Failed to version information resource. " + + VERSION_PROPERTY_FILE + " not found."); + } + + Properties properties = new Properties(); + properties.load(resource); + + product = properties.getProperty("server.product", "n/a"); + version = properties.getProperty("server.version", "n/a"); + + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static String product; + private static String version; + + private VersionProperties() { + } + + public static String getProduct() { + return product; + } + + public static String getVersion() { + return version; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/java/org/apache/tamaya/server/spi/ScopeManager.java ---------------------------------------------------------------------- diff --git a/server/src/main/java/org/apache/tamaya/server/spi/ScopeManager.java b/server/src/main/java/org/apache/tamaya/server/spi/ScopeManager.java new file mode 100644 index 0000000..3d2757a --- /dev/null +++ b/server/src/main/java/org/apache/tamaya/server/spi/ScopeManager.java @@ -0,0 +1,84 @@ +/* + * 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.tamaya.server.spi; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.ConfigOperator; +import org.apache.tamaya.spi.ServiceContextManager; + +/** + * Singleton manager for scopes, used by the server component to filtering returned configurations. + */ +public final class ScopeManager { + /** The logger used. */ + private static final Logger LOG = Logger.getLogger(ScopeManager.class.getName()); + + private static Map<String, ScopeProvider> scopeProviders = initProviders(); + + /** + * Singleton constructor. + */ + private static Map<String, ScopeProvider> initProviders(){ + final Map<String, ScopeProvider> result = new ConcurrentHashMap<>(); + for(final ScopeProvider prov: ServiceContextManager.getServiceContext().getServices(ScopeProvider.class)){ + try{ + result.put(prov.getScopeType(), prov); + } catch(final Exception e){ + LOG.log(Level.WARNING, "Error loading scopes from " + prov, e); + } + } + return result; + } + + /** + * Singleton constructor. + */ + private ScopeManager(){} + + /** + * Get the scope given its id and provider. + * + * @param scopeId the scope name + * @param targetScope name of the targetScope + * @return the scope matching + * @throws ConfigException if no such scope is defined + */ + public static ConfigOperator getScope(String scopeId, String targetScope) + throws ConfigException { + final ScopeProvider prov = scopeProviders.get(scopeId); + if(prov==null){ + throw new ConfigException("No such scope: " + scopeId); + } + return prov.getScope(targetScope); + } + + /** + * Get the defined scope names. + * @return the defined scope names, never null. + */ + public static Set<String> getScopes(){ + return scopeProviders.keySet(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/java/org/apache/tamaya/server/spi/ScopeProvider.java ---------------------------------------------------------------------- diff --git a/server/src/main/java/org/apache/tamaya/server/spi/ScopeProvider.java b/server/src/main/java/org/apache/tamaya/server/spi/ScopeProvider.java new file mode 100644 index 0000000..5c247d8 --- /dev/null +++ b/server/src/main/java/org/apache/tamaya/server/spi/ScopeProvider.java @@ -0,0 +1,40 @@ +/* + * 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.tamaya.server.spi; + +import org.apache.tamaya.ConfigOperator; + +/** + * Simple registrable provider class to register scopes for the server extension. + */ +public interface ScopeProvider { + + /** + * Access the unique scope name. + * @return the unique scope name. + */ + String getScopeType(); + + /** + * Return the scope operator that implements the scope for the given scope id. + * @param scopeId target scope id. + * @return the scope operator, never null. + */ + ConfigOperator getScope(String scopeId); +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/resources/META-INF/tamaya-server-version.properties ---------------------------------------------------------------------- diff --git a/server/src/main/resources/META-INF/tamaya-server-version.properties b/server/src/main/resources/META-INF/tamaya-server-version.properties new file mode 100644 index 0000000..ef0ca70 --- /dev/null +++ b/server/src/main/resources/META-INF/tamaya-server-version.properties @@ -0,0 +1,22 @@ + +# +# 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 current 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. +# + +server.product=Apache Tamaya +server.version=${project.version} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/resources/banner.txt ---------------------------------------------------------------------- diff --git a/server/src/main/resources/banner.txt b/server/src/main/resources/banner.txt new file mode 100644 index 0000000..4e2714b --- /dev/null +++ b/server/src/main/resources/banner.txt @@ -0,0 +1,14 @@ + + ââââââ âââââââ ââââââ ââââââââââ âââââââââââ âââââââââ ââââââ ââââ ââââ ââââââ âââ âââ ââââââ +âââââââââââââââââââââââââââââââââââ âââââââââââ ââââââââââââââââââââââ âââââââââââââââââ ââââââââââââ +âââââââââââââââââââââââââââ ââââââââââââââ âââ âââââââââââââââââââââââââââ âââââââ ââââââââ +âââââââââââââââ âââââââââââ ââââââââââââââ âââ âââââââââââââââââââââââââââ âââââ ââââââââ +âââ ââââââ âââ ââââââââââââââ âââââââââââ âââ âââ ââââââ âââ ââââââ âââ âââ âââ âââ +âââ ââââââ âââ âââ ââââââââââ âââââââââââ âââ âââ ââââââ ââââââ âââ âââ âââ âââ + âââââââââââââââââââââââ âââ ââââââââââââââââââ + âââââââââââââââââââââââââââ âââââââââââââââââââ + ââââââââââââââ âââââââââââ âââââââââ ââââââââ + ââââââââââââââ ââââââââââââ ââââââââââ ââââââââ + âââââââââââââââââââ âââ âââââââ âââââââââââ âââ + âââââââââââââââââââ âââ âââââ âââââââââââ âââ + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/server/src/main/resources/config-server.yml ---------------------------------------------------------------------- diff --git a/server/src/main/resources/config-server.yml b/server/src/main/resources/config-server.yml new file mode 100644 index 0000000..2e210a6 --- /dev/null +++ b/server/src/main/resources/config-server.yml @@ -0,0 +1,31 @@ +# +# 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 current 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. +# + +scope: java + +server: + applicationConnectors: + - type: http + port: 4001 + adminConnectors: + - type: http + port: 4099 + + # ${project.version} +
