Spring has made it so incredibly easy to set up application properties...but how would you do it without Spring?
I need to deploy a Java / Groovy application to a server where using Spring is out of the question... and I also don't have the liberty to install anything like Redis either. One option I am considering is to set up a Spring Cloud Config Server elsewhere and have my application consume properties from the config server. Trouble is, that is a bit of an overkill for my project now.
Could anyone suggest a way to do this in good, old, plain Java? :)
This is a really simple and basic example, but you can modify it as you like:
PropertyConfigurator.java
public class PropertiesConfigurator
{
Properties properties = new Properties();
String configInputPath = null;
InputStream configInputStream = null;
public PropertiesConfigurator(String configInputPath)
{
this.configInputPath = configInputPath;
}
public PropertiesConfigurator load() throws IOException, PropertyException
{
try
{
this.configInputStream = new FileInputStream(this.configInputPath);
// load a properties file
this.properties.load(this.configInputStream);
validate();
}
catch (IOException ex)
{
System.out.println("Failed load properties file: " + this.configInputPath);
throw ex;
}
catch (PropertyException ex)
{
System.out.println("One or more properties are empty");
throw ex;
}
finally
{
if (this.configInputStream != null)
{
try
{
this.configInputStream.close();
}
catch (IOException ex)
{
System.out.println("Failed to close input stream");
throw ex;
}
}
}
return this;
}
private void validate() throws PropertyException
{
Enumeration<?> e = this.properties.propertyNames();
while (e.hasMoreElements())
{
String key = (String) e.nextElement();
String value = this.properties.getProperty(key);
if (value.isEmpty())
{
System.out.println(String.format("Property %s is empty!", key));
throw new PropertyException("One or more properties are empty");
}
}
}
public String getProperty(String key)
{
return this.properties.getProperty(key);
}
#Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof PropertiesConfigurator))
return false;
PropertiesConfigurator that = (PropertiesConfigurator) o;
if (properties != null ? !properties.equals(that.properties) : that.properties != null)
return false;
if (configInputPath != null ? !configInputPath.equals(that.configInputPath) : that.configInputPath != null)
return false;
return configInputStream != null ?
configInputStream.equals(that.configInputStream) :
that.configInputStream == null;
}
#Override
public int hashCode()
{
int result = properties != null ? properties.hashCode() : 0;
result = 31 * result + (configInputPath != null ? configInputPath.hashCode() : 0);
result = 31 * result + (configInputStream != null ? configInputStream.hashCode() : 0);
return result;
}
}
PropertyException.java
public class PropertyException extends Exception
{
public PropertyException()
{
}
public PropertyException(String message)
{
super(message);
}
public PropertyException(String message, Throwable throwable)
{
super(message, throwable);
}
}
MainRunner.java
public class MainRunner
{
public static void main(String[] args)
{
try
{
String configFilePath = "application.properties";
PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator(configFilePath).load();
String prop1 = propertiesConfigurator.getProperty("keyprop1");
// Do whatever you want with prop1
// ...
}
catch (PropertyException ex)
{
System.out.println("Failed to load properties");
System.exit(1);
}
catch (Exception ex)
{
System.out.println("Error in main application");
System.exit(1);
}
}
}
Example of application.properties
keyprop1=value1
keyprop2=value2
Again, it's very basic and you should definitely improve this code and add your logic, validation, etc.
Take a look at http://constretto.org. That's easy to use configuration framework.
Related
I am writing code to unmarshal XML from a file. I don't know up front which schema the XML is based on so I try to unmarshal it with several schemas in the form of different Jaxb2Marshaller instances.
The method needs to:
attempt to unmarshal the XML with each marshaller
If this succeeds, return the resulting object
If it fails, try the next marshaller
If all marshallers fail, throw an exception with the last error message
Here is the current code:
private Object getObject(final byte[] data) throws MyException {
String lastErrorMessage = "";
for (final Jaxb2Marshaller marshaller : this.marshallers) {
try {
return marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(data)));
} catch (final XmlMappingException e) {
LOGGER.warn("Invalid XML", e);
lastErrorMessage = e.getMessage();
}
}
throw new MyException(lastErrorMessage);
}
I feel this method does too many things at different levels of abstraction:
iterate over marshallers
apply a marshaller
return result
catch exceptions
throw exception
But I don't see a way to simplify it. The try-catch block is needed for every marshaller (because I should catch and ignore these XmlMappingExceptions except the last one). That block either returns a result object, or the lastErrorMessage, which is needed below the iteration to throw the MyException.
The only solution I can think of is to create some contrived Result class which contains either the result object or the error message but that feels cludgy. Any other insights?
I would like methods with a granularity like these:
private Object getObject(byte[] data) throws MyException {
Result result;
for (Jaxb2Marshaller marshaller : this.marshallers) {
result = getObject(marshaller, data);
}
return handleError(result);
}
private Result getObject(Jaxb2Marshaller marshaller, byte[] data) {
try {
return Result.value(marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(data))));
} catch (final XmlMappingException e) {
LOGGER.warn("Invalid XML", e);
return Result.error(e.getMessage());
}
}
private Object handleError(Result result) {
if (result.isError()) {
throw new MyException(result.errroMessage);
}
else {
return result.value;
}
}
But the additional Result class is verbose and cludgy:
private class Result {
String errorMessage;
Object value;
static Result error(String errorMessage) {
Result result = new Result();
result.errorMessage = errorMessage;
return result;
}
static Result value(Object value) {
Result result = new Result();
result.value = value;
return result;
}
boolean isError() {
return errorMessage != null;
}
}
How about this?
public class MultiUnmarshaller {
private final List<Jaxb2Marshaller> marshallers;
private Object value;
private String error;
public MultiUnmarshaller(List<Jaxb2Marshaller> marshallers) {
this.marshallers = marshallers;
}
private void init() {
error = "No marshallers available";
value = null;
}
public Object getObject(byte[] data) throws MyException {
init();
Iterator<Jaxb2Marshaller> it = marshallers.iterator();
while(it.hasNext() && errorMessage != null) {
unmarshalObject(marshaller, data);
}
return produceResult();
}
private void unmarshalObject(Jaxb2Marshaller marshaller, byte[] data) {
try {
value = marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(data)));
error = null;
} catch (final XmlMappingException e) {
LOGGER.warn("Invalid XML", e);
error = e.getMessage();
}
}
private Object produceResult() {
if (error == null) {
return value;
}
else {
throw new MyException(error);
}
}
}
I have this:
propiedades = new Properties();
try {
entrada = new FileInputStream("config.properties");
propiedades.load(entrada);
Set set =propiedades.stringPropertyNames();
System.out.println(set);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (entrada != null) {
try {
entrada.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
So basiclly whats inside "config.properties" is the following thing:
admin=admin
user=userpass
user1:userpas1
Next up,i have this code:
public boolean revisarCredenciales(String userName,String password)
{
Enumeration<?> e = propiedades.propertyNames();
while(e.hasMoreElements())
{
for (; e.hasMoreElements();) {
System.out.println(e.nextElement());
if (e.nextElement().equals(userName) && propiedades.getProperty(userName).equals(password))
{
return true;
}
}
}
return false;
}
In this block I tried to do a simple if where if the e.nextElement() equals userName (userName is just a txUserBox.getText()) and propiedades.getProperty(userName).equals(password(txPassword.getPassword()) then it returns a value, can be either false or true, and the method where it is called will access the program if true.
The problem comes where it always is returns true and with this, doesn't matter what I put on the textboxes it will log me on..
To quick answer in case somebody has an error like this i fixed this by just doing:
public boolean revisarCredenciales(String userName,String password,Set<String> setNombres)
{
for (String key:setNombres)
{
String pass=propiedades.getProperty(key);
if( userName.equals(key) && pass.equals(password)){
return true;
}
}
return false;
}
On the method to review credentials, because it was getting the setNombres from this:
propiedades = new Properties();
try {
entrada = new FileInputStream("config.properties");
propiedades.load(entrada);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (entrada != null) {
try {
entrada.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
setNombres=propiedades.stringPropertyNames();
So, when it reviews the credentials it receives first the username from the JTextField, the password and the set of strings. When u send the JPassword field do new String(txPassword.getPassword()). If you dont do it and send just txPassword.getPassword() it will send the password encripted and cant match then.
public boolean revisarCredenciales(String userName,String password,Set<String> setNombres)
{
for (key:setNombre(Position))
{
pass(the one written on the textbox)= propiedades.getProperty(key);
//Here it obtaions the value in properties, next to key
if( userName.equals(key) && pass.equals(password)){
//UserName is what is in the textbox /pass defined on top.equals(What is on the textbox)
return true;
//if that happens returns true, this proccess repeats for each line of the config.properties
}
}
return false;
}
Let's imagine that we have to call one method from different classes by definition coming at run time. For example we receive JSON like this:
{"calculator": "MyClass1", "parameter1": 1.0, "parameter2": 2.0, ... }
MyClass1 and more classes either extend some base class or implement some interface (just to be able to enumerate them at run time). We have to create object, pass the parameters to the object and call calculate() method.
I can think of two ways to do this:
switch(calculatorString) { case "MyClass1": calculator = new MyClass1(); ...
using Java Reflection
The first way is really stupid because the code must be updated each time new calculator class is added to the project. The second way is slightly better, but the IDE is unable to catch any type errors we make while creating the objects and invoking the method.
Are there other ways to do this (possibly better)?
You could maybe use the Java Service Provider Interface:
See for example here:
https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
https://www.baeldung.com/java-spi
It allows for different implementations/services that you implement (you still need class names in the services files) but don't have to use reflection directly.
You define the service providers in the META-INF
META-INF/services/com.baeldung.rate.spi.ExchangeRateProvider
Your services can register themselves and if you allow them to use the input json, you will be very flexible.
Reflection is the best way to create object at runtime, but its all depend on usecase.
You can also use factory design pattern for object Creation.
Using ReflectionAPI
try {
cls = Class.forName(className);
instance = cls.newInstance();
instance = BeanUtils.populateBean(properties, cls);
} catch (Exception e) {
e.printStackTrace();
}
BeanUtil Class
public static Object populateBean(Map<String, Object> propertyMap, Class<?> clazz) throws Exception {
PropertyUtilsBean bean = new PropertyUtilsBean();
Object obj = null;
try {
obj = clazz.newInstance();
for(Map.Entry<String, Object> entrySet: propertyMap.entrySet()) {
PropertyDescriptor descriptor = null;
try {
descriptor =
bean.getPropertyDescriptor(obj, entrySet.getKey());
if (descriptor == null) {
continue;
}
Method writeMethod = bean.getWriteMethod(descriptor);
writeMethod.invoke(obj, convert(descriptor.getPropertyType(), entrySet.getValue(), DATE_PATTERN));
} catch (IncompatibleConversion e) {
throw e;
} catch (Exception e) {
throw new Exception("Unable to parse");
}
}
}catch(Exception e) {
throw e;
}
return obj;
}
Convert Class
private static Object convert(Class<?> clzz, Object value, String datePattern) throws Exception {
if (clzz.isAssignableFrom(BigInteger.class)) {
if (value == null) {
return value;
}
if (value instanceof BigInteger) {
return value;
}
try {
return new BigInteger(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(BigDecimal.class)) {
if (value == null) {
return value;
}
if (value instanceof BigDecimal) {
return parseBigDecimal(value.toString(), DECIMAL_PRECISION);
}
try {
return parseBigDecimal(value.toString(), DECIMAL_PRECISION);
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Integer.class)) {
if (value == null) {
return value;
}
if (value instanceof Integer) {
return value;
}
try {
return new Integer(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Long.class)) {
if (value == null) {
return value;
}
if (value instanceof Long) {
return value;
}
try {
return new Long(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(String.class)) {
if (value == null) {
return value;
}
if (value instanceof String) {
return value;
}
try {
return value.toString();
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Date.class)) {
if (value == null) {
return value;
}
if (value instanceof Date) {
return value;
}
if (datePattern == null) {
throw new Exception("date pattern cannot be null");
}
try {
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
return sdf.parse(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Byte.class)) {
if (value == null) {
return value;
}
if (value instanceof Byte) {
return (value);
} else if (value instanceof Number) {
return new Byte(((Number) value).byteValue());
}
try {
return (new Byte(value.toString()));
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Float.class)) {
if (value == null) {
return value;
}
if (value instanceof Float) {
return (value);
}
try {
return new Float(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
} else if (clzz.isAssignableFrom(Double.class)) {
if (value == null) {
return value;
}
if (value instanceof Double) {
return (value);
}
try {
return new Double(value.toString());
} catch (Exception e) {
throw new IncompatibleConversion(e);
}
}
throw new Exception("Incompactible Conversion");
}
I'm a novice when it comes to JSPs and JAVA.
How do I get the output from the below code to display on a jsp, considering that it runs everything from the main and contains non-public methods, a nested static class etc?
I know that we are not supposed to use java code on jsp but my first step in this proof on concept exercise is to get the code running and returning data from a backend then I can set about using EL etc.
I can run the program, with the correct config settings, from within Eclipse and all works fine with the output appearing on the console but I'm really not sure how to access it from within a jsp.
How do I access the static class and static methods from a jsp if they aren't public?
All help greatly appreciated.
public class CustomDestinationDataProvider
{
static class MyDestinationDataProvider implements DestinationDataProvider
{
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName)
{
try
{
//read the destination from DB
Properties p = secureDBStorage.get(destinationName);
if(p!=null)
{
//check if all is correct, for example
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
}
catch(RuntimeException re)
{
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener)
{
this.eL = eventListener;
}
public boolean supportsEvents()
{
return true;
}
//implementation that saves the properties in a very secure way
void changeProperties(String destName, Properties properties)
{
synchronized(secureDBStorage)
{
if(properties==null)
{
if(secureDBStorage.remove(destName)!=null)
eL.deleted(destName);
}
else
{
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
} // end of MyDestinationDataProvider
//business logic
void executeCalls(String destName)
{
JCoDestination dest;
try
{
dest = JCoDestinationManager.getDestination(destName);
dest.ping();
System.out.println("Destination " + destName + " works");
step4WorkWithTable(dest);
}
catch(JCoException e)
{
e.printStackTrace();
System.out.println("Execution on destination " + destName+ " failed");
}
}
static Properties getDestinationPropertiesFromUI()
{
//adapt parameters in order to configure a valid destination
Properties connectProperties = new Properties();
// Add code here to set config settings
return connectProperties;
}
public static void main(String[] args)
{
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
//register the provider with the JCo environment;
//catch IllegalStateException if an instance is already registered
try
{
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
}
catch(IllegalStateException providerAlreadyRegisteredException)
{
//somebody else registered its implementation,
//stop the execution
throw new Error(providerAlreadyRegisteredException);
}
String destName = "????";
CustomDestinationDataProvider test = new CustomDestinationDataProvider();
//set properties for the destination and ...
myProvider.changeProperties(destName, getDestinationPropertiesFromUI());
//... work with it
test.executeCalls(destName);
}
public static void step4WorkWithTable(JCoDestination dest) throws JCoException
{
JCoFunction function = dest.getRepository().getFunction("BAPI_COMPANYCODE_GETLIST");
if(function == null)
throw new RuntimeException("BAPI_COMPANYCODE_GETLIST not found in SAP.");
try
{
function.execute(dest);
}
catch(AbapException e)
{
System.out.println(e.toString());
return;
}
JCoStructure returnStructure = function.getExportParameterList().getStructure("RETURN");
if (! (returnStructure.getString("TYPE").equals("")||returnStructure.getString("TYPE").equals("S")) )
{
throw new RuntimeException(returnStructure.getString("MESSAGE"));
}
JCoTable codes = function.getTableParameterList().getTable("COMPANYCODE_LIST");
for (int i = 0; i < codes.getNumRows(); i++)
{
codes.setRow(i);
System.out.println(codes.getString("COMP_CODE") + '\t' + codes.getString("COMP_NAME"));
}
//move the table cursor to first row
codes.firstRow();
for (int i = 0; i < codes.getNumRows(); i++, codes.nextRow())
{
function = dest.getRepository().getFunction("BAPI_COMPANYCODE_GETDETAIL");
if (function == null)
throw new RuntimeException("BAPI_COMPANYCODE_GETDETAIL not found in SAP.");
function.getImportParameterList().setValue("COMPANYCODEID", codes.getString("COMP_CODE"));
//We do not need the addresses, so set the corresponding parameter to inactive.
//Inactive parameters will be either not generated or at least converted.
function.getExportParameterList().setActive("COMPANYCODE_ADDRESS",false);
try
{
function.execute(dest);
}
catch (AbapException e)
{
System.out.println(e.toString());
return;
}
returnStructure = function.getExportParameterList().getStructure("RETURN");
if (! (returnStructure.getString("TYPE").equals("") ||
returnStructure.getString("TYPE").equals("S") ||
returnStructure.getString("TYPE").equals("W")) )
{
throw new RuntimeException(returnStructure.getString("MESSAGE"));
}
JCoStructure detail = function.getExportParameterList().getStructure("COMPANYCODE_DETAIL");
System.out.println(detail.getString("COMP_CODE") + '\t' +
detail.getString("COUNTRY") + '\t' +
detail.getString("CITY"));
}//for
}
}
So I'm working with JSON in Java and JSON can have a base of either an Array or an Object. In my Config class, I take a class as an argument so I can create the file accordingly if it doesn't exist. I also store the class as a private field so I know in future.
However, when I get to reading the file, I'd prefer to have multiple return types though the same method name. If I return Object, I then have to cast the returned value which I want to avoid.
Current code:
public class Config {
private File dir = null;
private File file = null;
private Class clazz = null;
public Config(String program, String fileName, Class root) throws IOException {
this.dir = new File(System.getProperty("user.home") + File.separator + program);
if (!this.dir.exists()) {
this.dir.mkdir();
}
this.file = new File(this.dir + File.separator + fileName);
if (!this.file.exists()) {
this.file.createNewFile();
if (root.getName().equals(JSONArray.class.getName())) {
Files.write(this.file.toPath(), "[]".getBytes());
} else if (root.getName().equals(JSONObject.class.getName())) {
Files.write(this.file.toPath(), "{}".getBytes());
}
}
this.clazz = root;
}
public JSONArray readConfig() {
return null;
}
public JSONObject readConfig() {
return null;
}
}
Is there anyway I can do what I want without having to return Object?
multiple return types though the same method name
well, it is possible to use generic function to achieve that. For example,
public static void main(String[] args) {
try {
String t = getObject(String.class);
Integer d = getObject(Integer.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static <T> T getObject(Class<T> returnType) throws Exception {
if(returnType == String.class) {
return (T) "test";
} else if(returnType == Integer.class) {
return (T) new Integer(0);
} else {
return (T) returnType.newInstance();
}
}
Will the following code even compile?
I'm afraid no. There are few compilation errors such as
public Object readConfig() {
try {
// Assume jsonString exists
return (this.clazz.getDeclaredConstructor(String.class).newInstance(jsonString)); <--- clazz should be getClass()
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
<---- missing return statement
}
}