TAMAYA-145: Added support for Refreshable and property source level filtering.
Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/commit/20c558d0 Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/tree/20c558d0 Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/diff/20c558d0 Branch: refs/heads/master Commit: 20c558d0771a07da54fe26a9a8f28b80cb06014e Parents: 7b9971d Author: anatole <anat...@apache.org> Authored: Wed Dec 21 16:58:40 2016 +0100 Committer: anatole <anat...@apache.org> Committed: Wed Dec 21 16:58:40 2016 +0100 ---------------------------------------------------------------------- .../org/apache/tamaya/metamodel/Enabled.java | 37 +++++ .../tamaya/metamodel/EnabledPropertySource.java | 135 +++++++++++++++++++ .../EnabledPropertySourceProvider.java | 112 +++++++++++++++ .../apache/tamaya/metamodel/MetaContext.java | 10 ++ .../metamodel/internal/ContextReader.java | 4 +- .../DSLLoadingConfigurationProviderSpi.java | 10 +- .../internal/PropertySourceReader.java | 78 +++++++++++ 7 files changed, 375 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/Enabled.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/Enabled.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/Enabled.java new file mode 100644 index 0000000..b3051d3 --- /dev/null +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/Enabled.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.metamodel; + +/** + * Common interface for items that can be enabled or disabled. + */ +public interface Enabled { + + /** + * Returns the enabled property. + * @return the enabled value. + */ + boolean isEnabled(); + + /** + * Enables/disables this property source. + * @param enabled the enabled value. + */ + void setEnabled(boolean enabled); +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySource.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySource.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySource.java new file mode 100644 index 0000000..e231930 --- /dev/null +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySource.java @@ -0,0 +1,135 @@ +/* + * 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; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + + +/** + * Wrapped property source that allows enabling a property source using an + * {@code enabled} expression. + */ +public final class EnabledPropertySource + implements PropertySource, Enabled { + + private static final Logger LOG = Logger.getLogger(EnabledPropertySource.class.getName()); + private String enabledExpression; + private PropertySource wrapped; + private boolean enabled; + + public EnabledPropertySource(PropertySource wrapped, Map<String,String> context, String expression) { + this.enabledExpression = Objects.requireNonNull(expression); + this.wrapped = Objects.requireNonNull(wrapped); + this.enabled = calculateEnabled(context); + } + + protected boolean calculateEnabled(Map<String, String> context) { + try { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("nashorn"); + if(engine==null){ + engine = manager.getEngineByName("rhino"); + } + // init script engine + for(Map.Entry<String,String> entry: context.entrySet()) { + engine.put(entry.getKey(), entry.getValue()); + } + Object o = engine.eval(enabledExpression); + if(!(o instanceof Boolean)){ + LOG.severe("Enabled expression must evaluate to Boolean: '" + +enabledExpression+"', but was " + o + + ", property source will be disabled: " + + wrapped.getName()); + return false; + } + return (Boolean)o; + } catch (ScriptException e) { + LOG.severe("Invalid Boolean expression: '" + +enabledExpression+"': " + e + ", property source will be disabled: " + + wrapped.getName()); + } + return false; + } + + /** + * Returns the enabled property. + * @return the enabled value. + */ + @Override + public boolean isEnabled(){ + return enabled; + } + + /** + * Enables/disables this property source. + * @param enabled the enabled value. + */ + @Override + public void setEnabled(boolean enabled){ + this.enabled = enabled; + } + + @Override + public int getOrdinal() { + return this.wrapped.getOrdinal(); + } + + @Override + public String getName() { + return this.wrapped.getName(); + } + + @Override + public PropertyValue get(String key) { + if(!isEnabled()){ + return null; + } + return this.wrapped.get(key); + } + + @Override + public Map<String, String> getProperties() { + if(!isEnabled()){ + return Collections.emptyMap(); + } + return this.wrapped.getProperties(); + } + + @Override + public boolean isScannable() { + return this.wrapped.isScannable(); + } + + @Override + public String toString() { + return "DynamicPropertySource{" + + "\n enabled=" + enabledExpression + + "\n wrapped=" + wrapped + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySourceProvider.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySourceProvider.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySourceProvider.java new file mode 100644 index 0000000..5dfed10 --- /dev/null +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/EnabledPropertySourceProvider.java @@ -0,0 +1,112 @@ +/* + * 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; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertySourceProvider; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +/** + * Wrapped property source provider that allows enabling a property source using an + * {@code enabled} expression. + */ +public final class EnabledPropertySourceProvider + implements PropertySourceProvider, Enabled { + + private static final Logger LOG = Logger.getLogger(EnabledPropertySourceProvider.class.getName()); + private String enabledExpression; + private PropertySourceProvider wrapped; + private boolean enabled; + + public EnabledPropertySourceProvider(PropertySourceProvider wrapped, Map<String,String> context, String expression) { + this.enabledExpression = Objects.requireNonNull(expression); + this.wrapped = Objects.requireNonNull(wrapped); + this.enabled = calculateEnabled(context); + } + + protected boolean calculateEnabled(Map<String, String> context) { + try { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("nashorn"); + if(engine==null){ + engine = manager.getEngineByName("rhino"); + } + // init script engine + for(Map.Entry<String,String> entry: context.entrySet()) { + engine.put(entry.getKey(), entry.getValue()); + } + Object o = engine.eval(enabledExpression); + if(!(o instanceof Boolean)){ + LOG.severe("Enabled expression must evaluate to Boolean: '" + +enabledExpression+"', but was " + o + + ", property source provider will be disabled: " + + wrapped.getClass().getName()); + return false; + } + return (Boolean)o; + } catch (ScriptException e) { + LOG.severe("Invalid Boolean expression: '" + +enabledExpression+"': " + e + ", property source provider will be disabled: " + + wrapped.getClass().getName()); + } + return false; + } + + /** + * Returns the enabled property. + * @return the enabled value. + */ + @Override + public boolean isEnabled(){ + return enabled; + } + + /** + * Enables/disables this property source. + * @param enabled the enabled value. + */ + @Override + public void setEnabled(boolean enabled){ + this.enabled = enabled; + } + + @Override + public Collection<PropertySource> getPropertySources() { + if(!isEnabled()){ + return Collections.emptySet(); + } + return this.wrapped.getPropertySources(); + } + + @Override + public String toString() { + return "DynamicPropertySourceProvider{" + + "\n enabled=" + enabledExpression + + "\n wrapped=" + wrapped + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/MetaContext.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/MetaContext.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/MetaContext.java index 4032c03..c4fa25a 100644 --- a/metamodel/src/main/java/org/apache/tamaya/metamodel/MetaContext.java +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/MetaContext.java @@ -52,6 +52,7 @@ public final class MetaContext { return new MetaContext(); } }; + public static final String DEFAULT_CONTEXT_NAME = "<DEFAULT>"; private String id; @@ -72,6 +73,15 @@ public final class MetaContext { } /** + * Access the default context. Contexts are managed as weak references in this class. If no + * such context exists, a new instance is created. + * @return the context instance, never null. + */ + public static MetaContext getDefaultInstance(){ + return getInstance(DEFAULT_CONTEXT_NAME); + } + + /** * Access a context by name. Contexts are managed as weak references in this class. If no * such valid context exists, a new instance is created, using the given {@code validSupplier}. * @param contextName the context name, not null. http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/ContextReader.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/ContextReader.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/ContextReader.java index 5754e4f..db72f13 100644 --- a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/ContextReader.java +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/ContextReader.java @@ -39,7 +39,7 @@ public class ContextReader implements MetaConfigurationReader { @Override public void read(Document document, ConfigurationContextBuilder contextBuilder) { NodeList nodeList = document.getDocumentElement().getElementsByTagName("context"); - String contextName = "DEFAULT"; + String contextName = null; LOG.finer("Reading " + nodeList.getLength() + " meta context entries..."); for(int i=0;i<nodeList.getLength();i++){ Node node = nodeList.item(i); @@ -48,7 +48,7 @@ public class ContextReader implements MetaConfigurationReader { if(nameNode!=null){ contextName = nameNode.getTextContent(); } - MetaContext context = MetaContext.getInstance(contextName); + MetaContext context = contextName!=null?MetaContext.getInstance(contextName):MetaContext.getDefaultInstance(); NodeList entryNodes = node.getChildNodes(); for(int c=0;c<entryNodes.getLength();c++){ Node entryNode = entryNodes.item(c); http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/DSLLoadingConfigurationProviderSpi.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/DSLLoadingConfigurationProviderSpi.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/DSLLoadingConfigurationProviderSpi.java index 266ed5a..47ce7f0 100644 --- a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/DSLLoadingConfigurationProviderSpi.java +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/DSLLoadingConfigurationProviderSpi.java @@ -23,18 +23,10 @@ import org.apache.tamaya.spi.*; import org.apache.tamaya.Configuration; import org.apache.tamaya.spisupport.DefaultConfiguration; import org.apache.tamaya.spisupport.DefaultConfigurationContextBuilder; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; import javax.annotation.Priority; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Comparator; import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; /** * ConfigurationContext that uses {@link MetaConfiguration} to configure the @@ -48,7 +40,7 @@ public class DSLLoadingConfigurationProviderSpi implements ConfigurationProvider @Override public ConfigurationContextBuilder getConfigurationContextBuilder() { - return ServiceContextManager.getServiceContext().getService(ConfigurationContextBuilder.class); + return ServiceContextManager.getServiceContext().create(ConfigurationContextBuilder.class); } @Override http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/20c558d0/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/PropertySourceReader.java ---------------------------------------------------------------------- diff --git a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/PropertySourceReader.java b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/PropertySourceReader.java index 8f42a3b..b3f107c 100644 --- a/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/PropertySourceReader.java +++ b/metamodel/src/main/java/org/apache/tamaya/metamodel/internal/PropertySourceReader.java @@ -18,10 +18,12 @@ */ package org.apache.tamaya.metamodel.internal; +import org.apache.tamaya.metamodel.*; import org.apache.tamaya.metamodel.spi.ItemFactory; import org.apache.tamaya.metamodel.spi.ItemFactoryManager; import org.apache.tamaya.metamodel.spi.MetaConfigurationReader; import org.apache.tamaya.spi.ConfigurationContextBuilder; +import org.apache.tamaya.spi.PropertyFilter; import org.apache.tamaya.spi.PropertySource; import org.apache.tamaya.spi.PropertySourceProvider; import org.w3c.dom.Document; @@ -66,6 +68,7 @@ public class PropertySourceReader implements MetaConfigurationReader{ PropertySource ps = sourceFactory.create(params); if(ps!=null) { ComponentConfigurator.configure(ps, params); + ps = decoratePropertySource(ps, contextBuilder, node, params); LOG.finer("Adding configured property source: " + ps.getName()); contextBuilder.addPropertySources(ps); } @@ -84,6 +87,7 @@ public class PropertySourceReader implements MetaConfigurationReader{ PropertySourceProvider prov = providerFactory.create(params); if(prov!=null) { ComponentConfigurator.configure(prov, node); + prov = decoratePropertySourceProvider(prov, contextBuilder, node, params); LOG.finer("Adding configured property source provider: " + prov.getClass().getName()); contextBuilder.addPropertySources(prov.getPropertySources()); } @@ -100,4 +104,78 @@ public class PropertySourceReader implements MetaConfigurationReader{ } } + /** + * Decorates a property source to be refreshable or filtered. + * @param ps the wrapped property source + * @param contextBuilder + *@param configNode the XML config node + * @param params the extracted parameter list @return the property source to be added to the context. + */ + private PropertySource decoratePropertySource(PropertySource ps, ConfigurationContextBuilder contextBuilder, Node configNode, Map<String, String> params){ + Node refreshableVal = configNode.getAttributes().getNamedItem("refreshable"); + if(refreshableVal!=null && Boolean.parseBoolean(refreshableVal.getNodeValue())){ + if(!(ps instanceof Refreshable)){ + ps = RefreshablePropertySource.of(params, ps); + } + } + NodeList childNodes = configNode.getChildNodes(); + for(int i=0;i<childNodes.getLength();i++){ + Node node = childNodes.item(i); + if("filter".equals(node.getNodeName())) { + ps = FilteredPropertySource.of(ps); + configureFilter((FilteredPropertySource) ps, node); + } + } + Node enabledVal = configNode.getAttributes().getNamedItem("enabled"); + if(enabledVal!=null){ + ps = new EnabledPropertySource(ps, + MetaContext.getDefaultInstance().getProperties(), + enabledVal.getNodeValue()); + } + return ps; + } + + private void configureFilter(FilteredPropertySource ps, Node filterNode) { + try { + String type = filterNode.getAttributes().getNamedItem("type").getNodeValue(); + ItemFactory<PropertyFilter> filterFactory = ItemFactoryManager.getInstance().getFactory(PropertyFilter.class, type); + if(filterFactory==null){ + LOG.severe("No such property filter: " + type); + return; + } + Map<String,String> params = ComponentConfigurator.extractParameters(filterNode); + PropertyFilter filter = filterFactory.create(params); + if(filter!=null) { + ComponentConfigurator.configure(filter, params); + LOG.finer("Adding configured property filter: " + filter.getClass().getName()); + ps.addPropertyFilter(filter); + } + }catch(Exception e){ + LOG.log(Level.SEVERE, "Failed to read property filter configuration: " + filterNode, e); + } + } + + /** + * Decorates a property source provider to be refreshable or filtered. + * @param prov the property source provider to be wrapped. + * @param contextBuilder + *@param configNode the XML config node + * @param params the extracted parameter list @return the property source provider to be added to the context. + */ + private PropertySourceProvider decoratePropertySourceProvider(PropertySourceProvider prov, ConfigurationContextBuilder contextBuilder, Node configNode, Map<String, String> params){ + Node refreshableVal = configNode.getAttributes().getNamedItem("refreshable"); + if(refreshableVal!=null && Boolean.parseBoolean(refreshableVal.getNodeValue())){ + if(!(prov instanceof Refreshable)){ + prov = RefreshablePropertySourceProvider.of(params, prov); + } + } + Node enabledVal = configNode.getAttributes().getNamedItem("enabled"); + if(enabledVal!=null){ + prov = new EnabledPropertySourceProvider(prov, + MetaContext.getDefaultInstance().getProperties(), + enabledVal.getNodeValue()); + } + return prov; + } + }