/*****************************************************************
* 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.cayenne.project.upgrade.v6;
import org.apache.cayenne.ConfigurationException;
import org.apache.cayenne.configuration.DataChannelDescriptor;
import org.apache.cayenne.configuration.DataNodeDescriptor;
import org.apache.cayenne.configuration.SAXNestedTagHandler;
import org.apache.cayenne.configuration.server.JNDIDataSourceFactory;
import org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory;
import org.apache.cayenne.conn.DataSourceInfo;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.resource.Resource;
import org.apache.cayenne.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A loader of Cayenne projects descriptor for version "3.0.0.1".
*/
class XMLDataChannelDescriptorLoader_V3_0_0_1 {
static final String DBCP_DATA_SOURCE_FACTORY = "org.apache.cayenne.configuration.server.DBCPDataSourceFactory";
private static Logger logger = LoggerFactory.getLogger(XMLDataChannelDescriptorLoader_V3_0_0_1.class);
static final String DOMAINS_TAG = "domains";
static final String DOMAIN_TAG = "domain";
static final String MAP_TAG = "map";
static final String NODE_TAG = "node";
static final String PROPERTY_TAG = "property";
static final String MAP_REF_TAG = "map-ref";
private static final Map<String, String> dataSourceFactoryLegacyNameMapping;
static {
dataSourceFactoryLegacyNameMapping = new HashMap<>();
dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.DriverDataSourceFactory",
XMLPoolingDataSourceFactory.class.getName());
dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.JNDIDataSourceFactory",
JNDIDataSourceFactory.class.getName());
dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.DBCPDataSourceFactory",
DBCP_DATA_SOURCE_FACTORY);
}
// implementation is statically typed and is intentionally not DI-provided
protected XMLDataMapLoader_V3_0_0_1 mapLoader;
protected XMLDataSourceInfoLoader_V3_0_0_1 dataSourceInfoLoader;
XMLDataChannelDescriptorLoader_V3_0_0_1() {
mapLoader = new XMLDataMapLoader_V3_0_0_1();
dataSourceInfoLoader = new XMLDataSourceInfoLoader_V3_0_0_1();
}
List<DataChannelDescriptor> load(Resource configurationSource) throws ConfigurationException {
if (configurationSource == null) {
throw new NullPointerException("Null configurationSource");
}
URL configurationURL = configurationSource.getURL();
List<DataChannelDescriptor> domains = new ArrayList<>();
try (InputStream in = configurationURL.openStream();) {
XMLReader parser = Util.createXmlReader();
DomainsHandler rootHandler = new DomainsHandler(configurationSource, domains, parser);
parser.setContentHandler(rootHandler);
parser.setErrorHandler(rootHandler);
parser.parse(new InputSource(in));
} catch (Exception e) {
throw new ConfigurationException("Error loading configuration from %s", e, configurationURL);
}
return domains;
}
/**
* Make sure the domain name is only made up of Java-identifier-safe
* characters.
*/
protected String scrubDomainName(String name) {
if (name == null || name.length() == 0) {
return name;
}
StringBuilder buffer = new StringBuilder(name.length());
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (i == 0 && !Character.isJavaIdentifierStart(c)) {
buffer.append('_');
} else if (i > 0 && !Character.isJavaIdentifierPart(c)) {
buffer.append('_');
} else {
buffer.append(c);
}
}
return buffer.toString();
}
/**
* Converts the names of standard Cayenne-supplied DataSourceFactories from
* the legacy names to the current names.
*/
private String convertDataSourceFactory(String dataSourceFactory) {
if (dataSourceFactory == null) {
return null;
}
String converted = dataSourceFactoryLegacyNameMapping.get(dataSourceFactory);
return converted != null ? converted : dataSourceFactory;
}
final class DomainsHandler extends SAXNestedTagHandler {
private Collection<DataChannelDescriptor> domains;
private Resource configurationSource;
DomainsHandler(Resource configurationSource, Collection<DataChannelDescriptor> domains, XMLReader parser) {
super(parser, null);
this.domains = domains;
this.configurationSource = configurationSource;
}
@Override
protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String qName,
Attributes attributes) {
if (localName.equals(DOMAINS_TAG)) {
return new DomainsChildrenHandler(parser, this);
}
return super.createChildTagHandler(namespaceURI, localName, qName, attributes);
}
}
final class DomainsChildrenHandler extends SAXNestedTagHandler {
private Collection<DataChannelDescriptor> domains;
private Resource configurationSource;
DomainsChildrenHandler(XMLReader parser, DomainsHandler parent) {
super(parser, parent);
this.domains = parent.domains;
this.configurationSource = parent.configurationSource;
}
@Override
protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
Attributes attributes) {
if (localName.equals(DOMAIN_TAG)) {
String domainName = attributes.getValue("", "name");
DataChannelDescriptor descriptor = new DataChannelDescriptor();
descriptor.setName(scrubDomainName(domainName));
descriptor.setConfigurationSource(configurationSource);
domains.add(descriptor);
return new DataChannelChildrenHandler(descriptor, parser, this);
}
logger.info(unexpectedTagMessage(localName, DOMAIN_TAG));
return super.createChildTagHandler(namespaceURI, localName, name, attributes);
}
}
final class DataChannelChildrenHandler extends SAXNestedTagHandler {
private DataChannelDescriptor descriptor;
DataChannelChildrenHandler(DataChannelDescriptor descriptor, XMLReader parser,
DomainsChildrenHandler parentHandler) {
super(parser, parentHandler);
this.descriptor = descriptor;
}
@Override
protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
Attributes attributes) {
if (localName.equals(PROPERTY_TAG)) {
String key = attributes.getValue("", "name");
String value = attributes.getValue("", "value");
if (key != null && value != null) {
descriptor.getProperties().put(key, value);
}
} else if (localName.equals(MAP_TAG)) {
String dataMapName = attributes.getValue("", "name");
Resource baseResource = descriptor.getConfigurationSource();
String dataMapLocation = attributes.getValue("", "location");
Resource dataMapResource = baseResource.getRelativeResource(dataMapLocation);
DataMap dataMap = mapLoader.load(dataMapResource);
dataMap.setName(dataMapName);
dataMap.setLocation(dataMapLocation);
dataMap.setConfigurationSource(dataMapResource);
descriptor.getDataMaps().add(dataMap);
} else if (localName.equals(NODE_TAG)) {
String nodeName = attributes.getValue("", "name");
if (nodeName == null) {
// TODO: assign dummy name?
throw new ConfigurationException("Error: <node> without 'name'.");
}
DataNodeDescriptor nodeDescriptor = new DataNodeDescriptor();
nodeDescriptor.setName(nodeName);
String dataSourceFactory = attributes.getValue("", "factory");
String dataSourceFactory6 = convertDataSourceFactory(dataSourceFactory);
nodeDescriptor.setDataSourceFactoryType(dataSourceFactory6);
// depending on the factory, "datasource" attribute is
// interpreted
// differently
String datasource = attributes.getValue("", "datasource");
if (XMLPoolingDataSourceFactory.class.getName().equals(dataSourceFactory6)) {
Resource baseResource = descriptor.getConfigurationSource();
Resource dataNodeResource = baseResource.getRelativeResource(datasource);
nodeDescriptor.setConfigurationSource(dataNodeResource);
DataSourceInfo dataSourceInfo = dataSourceInfoLoader.load(dataNodeResource);
nodeDescriptor.setDataSourceDescriptor(dataSourceInfo);
} else {
nodeDescriptor.setParameters(datasource);
}
descriptor.getNodeDescriptors().add(nodeDescriptor);
nodeDescriptor.setAdapterType(attributes.getValue("", "adapter"));
nodeDescriptor.setSchemaUpdateStrategyType(attributes.getValue("", "schema-update-strategy"));
return new DataNodeChildrenHandler(parser, this, nodeDescriptor);
}
return super.createChildTagHandler(namespaceURI, localName, name, attributes);
}
}
final class DataNodeChildrenHandler extends SAXNestedTagHandler {
private DataNodeDescriptor nodeDescriptor;
DataNodeChildrenHandler(XMLReader parser, SAXNestedTagHandler parentHandler, DataNodeDescriptor nodeDescriptor) {
super(parser, parentHandler);
this.nodeDescriptor = nodeDescriptor;
}
@Override
protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
Attributes attributes) {
if (localName.equals(MAP_REF_TAG)) {
String mapName = attributes.getValue("", "name");
nodeDescriptor.getDataMapNames().add(mapName);
}
return super.createChildTagHandler(namespaceURI, localName, name, attributes);
}
}
}