I created a DTO class that has a Map. I am processing this map using a custom XMLAdapter:
public class CmisObject {
public CmisObject() {
this.properties = new HashMap<String, CmisProperty>();
this.links = new ArrayList<String>();;
}
/* Here we override the default HashMap marshalling by Moxy */
#XmlAnyElement
#XmlJavaTypeAdapter(CmisPropertyMapAdapter.class)
private HashMap<String, CmisProperty> properties;
....
The adapter is defined like this:
#Override
public Element marshal(Map<String, CmisProperty> map) throws Exception {
Document document = documentBuilder.newDocument();
Element rootElement = document.createElement("properties");
document.appendChild(rootElement);
for(Entry<String,CmisProperty> entry : map.entrySet()) {
Element childElement = document.createElementNS("http://www.foo.com/bar/cmis", entry.getKey());
CmisProperty cp = entry.getValue();
childElement.setAttribute("id", cp.getId());
childElement.setAttribute("localName", cp.getLocalName());
...
rootElement.appendChild(childElement);
}
return rootElement;
....
and, finally I have package-info.java in the package where my DTO class is defined:
#XmlSchema(
namespace="http://www.foo.com/bar/baz",
xmlns = {
#XmlNs(namespaceURI = "http://www.foo.com/bar/cmis", prefix = "cmis")
},
elementFormDefault=XmlNsForm.QUALIFIED)
package com.foo.bar.beans;
import javax.xml.bind.annotation.*;
Everything works as desired, I am getting a nice JSON, however, no "cmis" prefixes are added.
When I registered the prefix mapping in my application:
...
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put("http://www.foo.com/bar/cmis", "cmis");
final MoxyJsonConfig moxyJsonConfig = new MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':');
final ContextResolver<MoxyJsonConfig> jsonConfigResolver = moxyJsonConfig.resolver();
register(jsonConfigResolver);
...
all needed prefixes were added.
As far as I understand, the registering step is not needed for MOXy if I have package-info.java defined.
What could be the problem here?
Thank you.
Related
I am trying to create xml output for web endpoint.
Building xml with ObjectNodes - and i fail to add node with the same name,
So i need
<feature_set>
<feature>
...
</feature>
<feature>
...
</feature>
....
I am trying to do like this, and it won't work:
ObjectNode featureSetNode = ((ObjectNode) sequenceNode).putObject("feature_set");
for (SimpleFeature feature : simpleFeatures ) {
JsonNode featureNode = featureSetNode.putObject("feature");
}
I am getting only one feature node as a result.
How can i add nodes with the same name?
You could let jackson automatically convert the object using annotations like this:
#JacksonXmlRootElement(localName = "feature_set")
public class FeatureRoot {
#JacksonXmlElementWrapper(useWrapping=false) //this removes wrapper tag
#JacksonXmlProperty(localName="feature")
private List<Feature> features;
public FeatureRoot(List<Feature> features) {
this.features = features;
}
public List<Feature> getFeatures() {
return features;
}
}
public class Feature {
private String name;
public Feature(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
The following code
FeatureRoot featureRoot = new FeatureRoot(
List.of(
new Feature("test"),
new Feature("asd")
)
);
XmlMapper mapper = new XmlMapper();
String xml = mapper.writeValueAsString(featureRoot);
will produce
<feature_set>
<feature>
<name>test</name>
</feature>
<feature>
<name>asd</name>
</feature>
</feature_set>
To create same-keys entries from scratch use this:
ObjectNode featureSetNode = sequenceNode.putObject("feature_set");
ArrayNode featureNode = featureSetNode.putArray("feature");
for (SimpleFeature feature : simpleFeatures ) {
ObjectNode featureNodeImpl = featureNode.addObject();
featureNodeImpl.put("start", feature.getSeqRegionStart());
}
XmlMapper mapper = new XmlMapper();
String xml = mapper.writeValueAsString(featureRoot);
Sorry for the foggy title, I know it does not tell much.
Please consider the following xsd type definition:
<xsd:complexType name="TopicExpressionType" mixed="true">
<xsd:sequence>
<xsd:any processContents="lax" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Dialect" type="xsd:anyURI" use="required"/>
<xsd:anyAttribute/>
</xsd:complexType>
Complete XSD: http://docs.oasis-open.org/wsn/b-2.xsd
Corresponding JAXB generated Java class:
package org.oasis_open.docs.wsn.b_2;
import org.w3c.dom.Element;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "TopicExpressionType", propOrder = {
"content"
})
public class TopicExpressionType {
#XmlMixed
#XmlAnyElement(lax = true)
protected List<Object> content;
#XmlAttribute(name = "Dialect", required = true)
#XmlSchemaType(name = "anyURI")
protected String dialect;
#XmlAnyAttribute
private Map<QName, String> otherAttributes = new HashMap<QName, String>();
public List<Object> getContent() {
if (content == null) {
content = new ArrayList<Object>();
}
return this.content;
}
public String getDialect() {
return dialect;
}
public void setDialect(String value) {
this.dialect = value;
}
public Map<QName, String> getOtherAttributes() {
return otherAttributes;
}
}
The first goal is to produce an XML like this with JAXB:
<wsnt:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</wsnt:TopicExpression>
Please note the followings:
The value of the TopicExpression element is basically a query string that refers to QNames. Example: tns:t1/*/t3
The value of the TopicExpression element contains one or more QName like strings (tns:t1). It must be a string as in the example, it cannot be an Element (e.g.: <my-expresseion>tns:t1/*/t3<my-expresseion/>)
The value of the TopicExpression element is an arbitrary string (at least from the schema's perspective, it follows the rules defined here: https://docs.oasis-open.org/wsn/wsn-ws_topics-1.3-spec-os.pdf page 18)
Even though the value is a string, I need to define the corresponding name space declarations. So if I have an expression like this:
tns:t1 then xmlns:tns has to be declared. If my expresseion is tns:t1/*/tns2:t3 then both xmlns:tns and xmlns:tns2 have to be declared.
The second goal is to get the value of TopicExpression on the other side together with the namespace, using JAXB.
I am completely stuck, I don't know how I could implement this. My only idea is to manually build the value for the TopicExpression and somehow tell the marshaller to include the related namespace declaration despite there is no actual element using it.
Update
Example for a complete SOAP request that includes the before mentioned TopicExpression:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<Action xmlns="http://www.w3.org/2005/08/addressing">http://docs.oasis-open.org/wsn/bw-2/NotificationProducer/SubscribeRequest</Action>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:57182d32-4e07-4f5f-8ab3-24838b3e33ac</MessageID>
</env:Header>
<env:Body>
<ns3:Subscribe xmlns:ns3="http://docs.oasis-open.org/wsn/b-2" xmlns:ns4="http://www.w3.org/2005/08/addressing" >
<ns3:ConsumerReference>
<ns4:Address>http://my-notification-consumer-url</ns4:Address>
</ns3:ConsumerReference>
<ns3:Filter>
<ns3:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple" xmlns:ns5="http://my.org/TopicNamespace" xmlns:ns6="http://extension.org/TopicNamespace">
ns5:t1/*/ns6:t3
<ns3:TopicExpression/>
</ns3:Filter>
</ns3:Subscribe>
</env:Body>
</env:Envelope>
Not sure, If I have understood your requirement correctly. See if this code sample is helpful for you. If not then maybe try to edit your question a bit and make me understand what exactly you are looking for. I will try to modify and update the code accordingly. Trying with a simple example would be better than providing the complete XSD. Also, look into the following methods: beforeMarshal and afterUnmarshal.
Following is the XML I am trying to marshal and unmarshal
<tns:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</tns:TopicExpression>
TopicExpressionType.class:
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class TestPojo {
#XmlValue
private String TopicExpression;
#XmlAnyAttribute
private Map<String, Object> anyAttributes = new HashMap<>();
}
Main.class:
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("topic.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(TestPojo.class).createUnmarshaller();
final TestPojo topic = unmarshaller.unmarshal(xmlStreamReader, TestPojo.class).getValue();
System.out.println(topic.toString());
StringWriter stringWriter = new StringWriter();
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("http://my.org/TopicNamespace", "tns");
Marshaller marshaller = JAXBContext.newInstance(TestPojo.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
QName qName = new QName("http://my.org/TopicNamespace","TopicExpression","tns");
JAXBElement<TestPojo> root = new JAXBElement<TestPojo>(qName, TestPojo.class, topic);
marshaller.marshal(root, stringWriter);
String result = stringWriter.toString();
System.out.println(result);
}
}
As you can see as of now I have populated the namespaces map directly. If this is dynamic then you can populate the same in a map and accordingly you can add it while marshaling.
This will provide the following output during the unmarshalling:
TestPojo(TopicExpression=
tns:t1/*/t3
, anyAttributes={Dialect=http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete})
During marshaling:
<tns:TopicExpression xmlns:tns="http://my.org/TopicNamespace" Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete">
tns:t1/*/t3
</tns:TopicExpression>
So the solution I've implemented:
Created a new TopicExpressionType class which has fields not only for the expression but for the namespaces too, used in the expression:
public class TopicExpressionType {
String dialect;
String expression;
List<Namespace> namespaces;
public TopicExpressionType(String dialect, String expression, List<Namespace> namespaces) {
this.dialect = dialect;
this.expression = expression;
this.namespaces = namespaces;
}
public static class Namespace {
String prefix;
String namespace;
public Namespace(String prefix, String namespace) {
this.prefix = prefix;
this.namespace = namespace;
}
public String getPrefix() {
return prefix;
}
public String getNamespace() {
return namespace;
}
}
}
Then implemented an XmlAdapter that is aware of the specifics, knows how to extract namespace prefixes from the expression string and it can read/write namespace declarations on the TopicExpression XML element:
public class TopicExpressionTypeAdapter extends XmlAdapter<Element, TopicExpressionType> {
#Override
public Element marshal(TopicExpressionType topicExpression) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://docs.oasis-open.org/wsn/b-2", "mns1:TopicExpression");
element.setAttribute("Dialect", topicExpression.getDialect());
element.setTextContent(topicExpression.getExpression());
for (var ns : topicExpression.namespaces) {
element.setAttribute("xmlns:" + ns.prefix, ns.namespace);
}
return element;
}
#Override
public TopicExpressionType unmarshal(Element arg0) throws Exception {
if (arg0.getFirstChild() instanceof Text text) {
var expression = text.getData();
if (expression == null || expression.isBlank())
throw new TopicExpressionAdapterException("Empty content");
// Extract the prefixes from the expression
var namespacePrefixes = new ArrayList<String>();
getNamespacePrefixes(expression, namespacePrefixes);
//Now get the namespaces for the prefixes
var nsMap = new ArrayList<TopicExpressionType.Namespace>();
for (var prefix : namespacePrefixes) {
var namespace = arg0.getAttribute("xmlns:" + prefix);
if (namespace == null || namespace.isBlank())
throw new TopicExpressionAdapterException("Missing namespace declaration for the following prefix: " + prefix);
nsMap.add(new TopicExpressionType.Namespace(prefix, namespace));
}
var dialect = arg0.getAttribute("Dialect");
if (dialect == null || dialect.isBlank())
throw new TopicExpressionAdapterException("Missing Dialect attribute");
return new TopicExpressionType(dialect, expression, nsMap);
} else {
throw new TopicExpressionAdapterException("Unexpected child element type: " + arg0.getFirstChild().getClass().getName());
}
}
public static class TopicExpressionAdapterException extends Exception {
public TopicExpressionAdapterException(String message) {
super(message);
}
}
}
Note: Implementation of the getNamespacePrefixes() method is left out intentionally from this answer.
The last step is to add the following annotation wherever the TopicExpressionType is used in JAXB generated classes:
#XmlJavaTypeAdapter(TopicExpressionTypeAdapter.class)
TopicExpressionType topicExpression;
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener
In our applications we are using property files very much. Since a few months I have started to learn Guava and I liked it a lot actually.
What is the best way to create a Map<String, Datasource> ?
The property file format is not strict. It can be changed if It can be expressed better with another format?
Sample property file:
datasource1.url=jdbc:mysql://192.168.11.46/db1
datasource1.password=password
datasource1.user=root
datasource2.url=jdbc:mysql://192.168.11.45/db2
datasource2.password=password
datasource2.user=root
The easiest thing is probably to use JSON rather than a properties file for this:
{
"datasources": [
{
"name": "datasource1",
"url": "jdbc:mysql://192.168.11.46/db1",
"user": "root",
"password": "password"
},
{
"name": "datasource2",
"url": "jdbc:mysql://192.168.11.46/db2",
"user": "root",
"password": "password"
}
]
}
Then you can just use a library such as Gson to convert that into objects:
public class DataSources {
private List<DataSourceInfo> dataSources;
public Map<String, DataSource> getDataSources() {
// create the map
}
}
public class DataSourceInfo {
private String name;
private String url;
private String user;
private String password;
// constructor, getters
}
Then to get the map:
Gson gson = new Gson();
Map<String, DataSource> dataSources = gson.fromJson(/* file or stream here */,
DataSources.class).getDataSources();
Properties class is a subclass of HashTable, which in turn implements Map.
You load it as usual with:
Properties properties = new Properties();
try {
properties.load(new FileInputStream("filename.properties"));
} catch (IOException e) {
}
edit: Ok so you want to transform it to Map<String, Datasource> ;)
//First convert properties to Map<String, String>
Map<String, String> m = Maps.fromProperties(properties);
//Sort them so that password < url < user for each datasource and dataSource1.* < dataSource2.*. In your case default string ordering is ok so we can take a normal treemap
Map<String, String> sorted = Maps.newTreeMap();
sorted.putAll(m);
//Create Multimap<String, List<String>> mapping datasourcename->[password,url, user ]
Function<Map.Entry<String, String>, String> propToList = new Function<String, Integer>() {
#Override
public String apply(Map.Entry<String, String> entry) {
return entry.getKey().split("\\.")[0];
}
};
Multimap<Integer, String> nameToParamMap = Multimaps.index(m.entrySet(), propToList);
//Convert it to map
Map<String, Collection<String>> mm = nameToParamMap.asMap();
//Transform it to Map<String, Datasource>
Map<String, Datasource> mSD = Maps.transformEntries(mm, new EntryTransformer<String, Collection<String>, DataSource>() {
public DataSource transformEntry(String key, Collection<String> value) {
// Create your datasource. You know by now that Collection<String> is actually a list so you can assume elements are in order: [password, url, user]
return new Datasource(.....)
}
};
//Copy transformed map so it's no longer a view
Map<String, Datasource> finalMap = Maps.newHashMap(mSD);
There's probably an easier way, but this should work :)
Still you're better off with json or xml. You can also load properties of different datasources from different files.
edit2: with less guava, more java:
//Sort them so that password < url < user for each datasource and dataSource1.* < dataSource2.*. In your case default string ordering is ok so we can take a normal SortedSet
SortedSet <String> sorted = new SortedSet<String>();
sorted.putAll(m.keySet);
//Divide keys into lists of 3
Iterable<List<String>> keyLists = Iterables.partition(sorted.keySet(), 3);
Map<String, Datasource> m = new HashMap<String, Datasource>();
for (keyList : keyLists) {
//Contains datasourcex.password, datasroucex.url, datasourcex.user
String[] params = keyList.toArray(new String[keyList.size()]);
String password = properties.get(params[0]);
String url = properties.get(params[1]);
String user = properties.get(params[2]);
m.put(params[0].split("\\.")[0], new DataSource(....)
}
If the file that you are using for configuration is not strict, you could use an XML file to store the defintion.
Example definition:
<resources>
<configuration>
<datasrouce>
<connection name="" url="" password="" user=""/>
<connection name="" url="" password="" user=""/>
</datasource>
</configuration>
</resources>
The using a Connection manager class you could just read the XML to obtain connection info and create an instance of connections and mange them.