/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* 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 org.ensembl.healthcheck.configurationmanager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.ensembl.healthcheck.ConfigurableTestRunner;
import uk.co.flamingpenguin.jewel.cli.OptionNotPresentException;
/**
* <p>
* Configuration class that gets its information from a List of other
* configuration objects. When a configuration object of type
* ConfigurationByCascading is asked for a parameter, it will iterate
* through its list of configuration objects and return the value
* it gets from the first configuration object that can provide one.
* </p>
*
* <p>
* Using this one can create a configuration system where one source
* overrides another like command line parameters overriding parameters
* from a property file or a property file overriding default settings in
* another property file or a combination of all of those.
* </p>
*
* <p>
* This object could implement the
* AbstractAliasAwareWithStanardInvocationHanderConfigurationBacking
* interface, but it doesn't seem necessary, since the individual objects
* are already aware of aliases.
* </p>
*
* @param <T>
*/
public class ConfigurationByCascading<T> extends AbstractConfigurationBacking {
static final Logger log = Logger.getLogger(ConfigurationByCascading.class.getCanonicalName());
private List<T> configurationObjects;
/**
* @param configurationObjects
*
* A list of configuration objects which will be used for the cascading
* configuration.
*
*/
public ConfigurationByCascading(List<T> configurationObjects) {
this.configurationObjects = configurationObjects;
}
/**
* @param configurationObjects
*
* An array of configuration objects which will be used for the cascading
* configuration.
*
*/
public ConfigurationByCascading(T[] configurationObjects) {
ArrayList<T> l = new ArrayList<T>();
for (T c : configurationObjects) {
l.add(c);
}
this.configurationObjects = l;
}
/**
* Creates a summary of the configuration objects used, calls toString()
* recursively on each of them so the user has an idea of what the
* configuration settings are.
*
* @see java.lang.Object#toString()
*
*/
public String toString() {
StringBuffer toString = new StringBuffer(this.getClass().getSimpleName() + " comprising of: ");
int order = 1;
for (T confObj : configurationObjects) {
toString.append("\n" + order + ".\n");
toString.append(confObj.toString());
order++;
}
return toString.toString();
}
/**
*
* This is called, when the user calls any of the get* or is* methods from
* the configuration interface. The calls are delegated to the
* configuration objects which are stored in the configurationObjects
* instance variable.
*
*/
public Object invoke(Object proxy, Method m, Object[] args) throws OptionNotPresentException {
Object result = null;
String methodName = m.getName();
Class<?>[] parameterTypes = m.getParameterTypes();
if (methodName.equals("toString")) { return toString(); }
boolean methodIsGetMethod = isGetMethod (m);
boolean methodIsIsMethod = isIsMethod (m);
try {
// Search in all configuration object in the order they were given
for (T configuration : configurationObjects) {
if (configuration == null) {
throw new NullPointerException("One of the configuration objects was null!");
}
try {
// If the configuraion object queried is not of the correct
// type, then invoking getMethod on its class will cause
// an NoSuchMethodException to be thrown. This will is
// caught below.
//
// This can happen, if the user is requesting information
// that this configuration object does not have. It is
// however possible that a different configuration object
// does have this method. Therefore it is correct that a
// particular configuration object throws the error, but
// this does not necessarily mean that calling the method
// on the cascading configuration object was wrong so the
// for loop in which we are now must continue to iterate
// through all the other objects.
//
Method methodInConfigObject = configuration.getClass().getMethod(
methodName,
parameterTypes
);
try {
// Try to invoke the method the user wants on the current
// configuration object. If it fails, because the parameter
// was not present in this object, the configuration object
// will throw a OptionNotPresentException which is wrapped
// in an InvocationTargetException.
//
result = methodInConfigObject.invoke(configuration);
if (methodIsGetMethod) {
// If we are here, all went well and we are returning
// the value of the parameter in the first
// configuration object in which it was set.
//
return result;
}
if (methodIsIsMethod) {
// If we are here, the method called is a method
// indicating if a specific parameter was set in the
// configuration. If the current configuration returns
// false, because it has not been set there, it might
// still have been set in one of the other
// configuration objects.
//
boolean boolResult = (Boolean) result;
if ( boolResult ) {
// If found we know it is there
return true;
}
// Otherwise continue for loop with other configuration
// objects
}
}
// The next two exceptions should never occur.
catch (IllegalArgumentException e) { throw new RuntimeException(e); }
catch (IllegalAccessException e) { throw new RuntimeException(e); }
catch (InvocationTargetException e) {
if (e.getTargetException() instanceof OptionNotPresentException) {
// In this case the variable was not found in the
// current configuration object, so proceed with next one.
} else {
if (e.getTargetException() instanceof UnsupportedOperationException) {
UnsupportedOperationException myE = (UnsupportedOperationException) e.getTargetException();
String errMsg = myE.getMessage() + "\n"
+ "This error probably occurrs because the "
+ "program is trying to access a method that "
+ "you have not annotated with @Option in the "
+ "configuration interface.\n";
log.log(java.util.logging.Level.SEVERE, errMsg, e);
throw new RuntimeException(errMsg, e);
} else {
// In any other case we have a problem.
throw new RuntimeException(e);
}
}
}
}
catch (NoSuchMethodException e) {
//
// This can happen, if the configuration object does not
// have the requested field.
//
}
}
}
// Should never happen, so throw an error.
catch (SecurityException e) { throw new RuntimeException(e); }
if (methodIsIsMethod) {
// If the method is an is ismethod, then we have searched through
// all configurations and not found any in which the parameter was
// true. Therefore false is returned.
//
return false;
}
// This would happen, if the user requests a configuration variable
// that has not been set in any of the configuration objects.
throw new OptionNotPresentException(
"Could not find configuration "
+ getVariableRequested(m)
+ " in any of the configuration objects.\n\n"
+ toString()
);
}
}