Skip to content

Commit

Permalink
Support conditional blocks in class based resource bundles. Fixes #212
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmiray committed Feb 18, 2017
1 parent a8fee2e commit 36eb8ae
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,94 @@ public class ApplicationModule extends AbstractModule {
}
----

== Conditional Blocks

There are times where a configuration value may dependent on an enviromental setting such as the current
<<_overview_metadata_environment,Application Environment>>. All types of configuration files (properties, Groovy scripts,
class based resource bundles) support the notion of conditional blocks. Take for example the following datasource configuration
in Groovy script format:

[source, groovy]
.griffon-app/resources/DataSource.groovy
----
dataSource.driverClassName = 'org.h2.Driver'
environments {
development {
dataSource.url = 'jdbc:h2:mem:@application.name@-dev'
}
test {
dataSource.url = 'jdbc:h2:mem:@application.name@-test'
}
production {
dataSource.url = 'jdbc:h2:file:/opt/@application.name@/data/db'
}
}
----

The same configuration in properties format:

[source, java]
.griffon-app/resources/DataSource.properties
----
dataSource.driverClassName = org.h2.Driver
environments.development.dataSource.url = jdbc:h2:mem:@application.name@-dev
environments.test.dataSource.url = jdbc:h2:mem:@application.name@-test
environments.production.dataSource.url = jdbc:h2:file:/opt/@application.name@/data/db
----

Or as a class based `ResourceBundle` subclass:

[source, java]
.griffon-app/conf/DataSource.java
----
import java.util.Map;
import griffon.util.AbstractMapResourceBundle;
import static griffon.util.CollectionUtils.map;
public class DataSource extends AbstractMapResourceBundle {
@Override
protected void initialize(Map<String, Object> entries) {
map(entries)
.e("dataSource", map()
.e("driverClassName", "org.h2.Driver")
)
.e("environments", map()
.e("development", map()
.e("dataSource", map()
.e("url", "jdbc:h2:mem:sample-dev")
)
)
.e("test", map()
.e("dataSource", map()
.e("url", "jdbc:h2:mem:sample-test")
)
)
.e("production", map()
.e("dataSource", map()
.e("url", "jdbc:h2:file:/opt/:sample/data/db")
)
)
);
}
}
----

The conditional block in this case is `environments`; the value of `{link_environment}` will dictate the actual value
associated with the `dataSource.url` key. There are 3 conditional blocks defined by default:

|====
| Name | Default value
| environments | Environment.getName()
| projects | Metadata.getApplicationName()
| platforms | GriffonApplicationUtils.getPlatform()
|====

You may define additional conditional blocks as needed, but you must do it by defining appropriate binding overrides
for the following types in a custom <<_overview_modules,Module>> of your choice:

* `griffon.util.PropertiesReader` for properties files.
* `griffon.util.ResourceBundleReader` for class based `ResourceBundle`
* `griffon.util.ConfigReader` for Groovy scripts.

3 changes: 2 additions & 1 deletion docs/griffon-site/src/jbake/content/news/griffon_2.10.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ The Griffon team is happy to announce the release of Griffon 2.10.0!
The following list summarizes the changes brought by this release:

* Modules can be evicted during bootstrap, avoiding their bindings from being contributed to the injector.
* New `PropertiesReader` class delivers conditional block support similar to what `ConfigReader` does for Groovy scripts.
* New `ResourceBundleReader` class delivers conditional block support to `java.util.ResourceBundle`, similar to what `ConfigReader` does for Groovy scripts.
* New `PropertiesReader` class delivers conditional block support to `java.util.Properties`, similar to what `ConfigReader` does for Groovy scripts.
* JavaFX Support
** JavaFX bindings, properties, and collections added in 2.9.1 have been relocated to other packages.
** New `ElementObservableList` class triggers updates when an element is updated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public class FooEvictorModule extends AbstractModule {
The evicting module (`FooEvictorModule`) has to have the same name as the evicted module (`FooModule`) hence why both
modules are annotated with `@Named("foo")`.

==== Enhanced Properties Support
==== Enhanced Properties and ResourceBundle Support

It's now possible to define conditional blocks in properties files, similarly as it's done in Groovy scripts. For example
the `datasource` plugin lets you define datasource configuration that may be environment sensible. In the Groovy version
of the configuration you'd write the following
It's now possible to define conditional blocks in properties files and class based resource bundles, similarly as it's
done in Groovy scripts. For example the `datasource` plugin lets you define datasource configuration that may be environment
sensible. In the Groovy version of the configuration you'd write the following

[source, groovy]
.DataSource.groovy
Expand Down Expand Up @@ -106,6 +106,45 @@ environments.test.dataSource.url = jdbc:h2:mem:@application.name@-test
environments.production.dataSource.url = jdbc:h2:file:/opt/@application.name@/data/db
----

Here's the class based ResourceBundle approach

[source, java]
.DataSource.java
----
import java.util.Map;
import griffon.util.AbstractMapResourceBundle;
import static griffon.util.CollectionUtils.map;
public class DataSource extends AbstractMapResourceBundle {
@Override
protected void initialize(Map<String, Object> entries) {
map(entries)
.e("dataSource", map()
.e("driverClassName", "org.h2.Driver")
)
.e("environments", map()
.e("development", map()
.e("dataSource", map()
.e("url", "jdbc:h2:mem:sample-dev")
)
)
.e("test", map()
.e("dataSource", map()
.e("url", "jdbc:h2:mem:sample-test")
)
)
.e("production", map()
.e("dataSource", map()
.e("url", "jdbc:h2:file:/opt/:sample/data/db")
)
)
);
}
}
----

==== JavaFX Support

All of the binding and property support classes added in `2.9.0` have been relocated form package `griffon.javafx.support`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed 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 griffon.util;

import griffon.core.env.Environment;
import griffon.core.env.Metadata;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ResourceBundle;

import static griffon.util.GriffonNameUtils.isBlank;
import static java.util.Objects.requireNonNull;

/**
* @author Andres Almiray
* @since 2.10.0
*/
public class ResourceBundleReader {
private static final String ENVIRONMENTS_METHOD = "environments";

private final Map<String, String> conditionValues = new LinkedHashMap<>();

public static class Provider implements javax.inject.Provider<ResourceBundleReader> {
@Inject private Metadata metadata;
@Inject private Environment environment;

@Override
public ResourceBundleReader get() {
ResourceBundleReader propertiesReader = new ResourceBundleReader();
propertiesReader.registerConditionalBlock("environments", environment.getName());
propertiesReader.registerConditionalBlock("projects", metadata.getApplicationName());
propertiesReader.registerConditionalBlock("platforms", GriffonApplicationUtils.getPlatform());
return propertiesReader;
}
}

public void registerConditionalBlock(@Nullable String blockName, @Nullable String blockValue) {
if (!isBlank(blockName)) {
if (isBlank(blockValue)) {
conditionValues.remove(blockName);
} else {
conditionValues.put(blockName, blockValue);
}
}
}

@Nonnull
public Map<String, String> getConditionalBlockValues() {
return Collections.unmodifiableMap(conditionValues);
}

@Nonnull
public String getEnvironment() {
return conditionValues.get(ENVIRONMENTS_METHOD);
}

public void setEnvironment(String environment) {
conditionValues.put(ENVIRONMENTS_METHOD, environment);
}

@Nonnull
public ResourceBundle read(final @Nonnull ResourceBundle bundle) {
requireNonNull(bundle, "Argument 'bundle' must not be null");
return new AbstractMapResourceBundle() {
@Override
protected void initialize(@Nonnull Map<String, Object> entries) {
entries.putAll(processBundle(bundle));
}
};
}

@Nonnull
protected Map<String, Object> processBundle(@Nonnull ResourceBundle input) {
Map<String, Object> output = new LinkedHashMap<>();

for (String key : input.keySet()) {
ConditionalBlockMatch match = resolveConditionalBlockMatch(key);
if (match != null) {
if (match.key != null) {
output.put(match.key, input.getObject(key));
}
} else {
output.put(key, input.getObject(key));
}
}

return output;
}

@Nullable
private ConditionalBlockMatch resolveConditionalBlockMatch(@Nonnull String key) {
for (Map.Entry<String, String> e : conditionValues.entrySet()) {
String blockName = e.getKey();
if (!key.startsWith(blockName + ".")) {
continue;
}

String prefix = blockName + "." + e.getValue() + ".";
if (key.startsWith(prefix)) {
String subkey = key.substring(prefix.length());
ConditionalBlockMatch match = resolveConditionalBlockMatch(subkey);
if (match == null) {
match = new ConditionalBlockMatch(subkey);
}
return match;
} else {
return new ConditionalBlockMatch(null);
}
}

return null;
}

private static class ConditionalBlockMatch {
public final String key;

private ConditionalBlockMatch(String key) {
this.key = key;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import griffon.core.view.WindowManager;
import griffon.util.CompositeResourceBundleBuilder;
import griffon.util.PropertiesReader;
import griffon.util.ResourceBundleReader;
import org.codehaus.griffon.runtime.core.addon.DefaultAddonManager;
import org.codehaus.griffon.runtime.core.artifact.ControllerArtifactHandler;
import org.codehaus.griffon.runtime.core.artifact.DefaultArtifactManager;
Expand Down Expand Up @@ -114,6 +115,10 @@ protected void doConfigure() {
.toProvider(PropertiesReader.Provider.class)
.asSingleton();

bind(ResourceBundleReader.class)
.toProvider(ResourceBundleReader.Provider.class)
.asSingleton();

bind(Context.class)
.withClassifier(named("applicationContext"))
.toProvider(DefaultContextProvider.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
package org.codehaus.griffon.runtime.util;

import griffon.core.resources.ResourceHandler;
import griffon.util.PropertiesResourceBundle;
import griffon.util.PropertiesReader;
import griffon.util.PropertiesResourceBundle;
import griffon.util.ResourceBundleReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -43,11 +44,13 @@ public class DefaultCompositeResourceBundleBuilder extends AbstractCompositeReso
protected static final String CLASS_SUFFIX = ".class";

protected final PropertiesReader propertiesReader;
protected final ResourceBundleReader resourceBundleReader;

@Inject
public DefaultCompositeResourceBundleBuilder(@Nonnull ResourceHandler resourceHandler, @Nonnull PropertiesReader propertiesReader) {
public DefaultCompositeResourceBundleBuilder(@Nonnull ResourceHandler resourceHandler, @Nonnull PropertiesReader propertiesReader, @Nonnull ResourceBundleReader resourceBundleReader) {
super(resourceHandler);
this.propertiesReader = propertiesReader;
this.resourceBundleReader = resourceBundleReader;
}

@Nonnull
Expand Down Expand Up @@ -88,7 +91,7 @@ protected Collection<ResourceBundle> loadBundleFromClass(@Nonnull String fileNam
try {
Class<?> klass = loadClass(className);
if (ResourceBundle.class.isAssignableFrom(klass)) {
bundles.add(newInstance(klass));
bundles.add(resourceBundleReader.read(newInstance(klass)));
}
} catch (ClassNotFoundException e) {
// ignore
Expand Down

0 comments on commit 36eb8ae

Please sign in to comment.