ConfigurationContainer.java
/*
* Copyright (C) 2011 Everit Kft. (http://www.everit.biz)
*
* 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.everit.persistence.lqmg.internal;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.everit.persistence.lqmg.LQMGException;
import org.everit.persistence.lqmg.internal.schema.xml.AbstractNamingRuleType;
import org.everit.persistence.lqmg.internal.schema.xml.ClassNameRuleType;
import org.everit.persistence.lqmg.internal.schema.xml.LQMGType;
import org.everit.persistence.lqmg.internal.schema.xml.NamingRulesType;
import org.everit.persistence.lqmg.internal.schema.xml.RegexRuleType;
import org.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleWiring;
import com.querydsl.sql.SchemaAndTable;
/**
* The configuration container used to code generation.
*/
public class ConfigurationContainer {
/**
* {@link ConfigValue} with <code>null</code>.
*/
private static class NullConfigValue extends ConfigValue<AbstractNamingRuleType> {
NullConfigValue() {
super(null, null, null);
}
}
private final Map<SchemaAndTable, ConfigValue<? extends AbstractNamingRuleType>> cache =
new HashMap<>();
private final Map<SchemaAndTable, ConfigValue<ClassNameRuleType>> classNameRuleMap =
new HashMap<>();
private final JAXBContext jaxbContext;
private final Map<SchemaAndTable, ConfigValue<ClassNameRuleType>> mainClassNameRuleMap =
new HashMap<>();
private final Map<SchemaAndTable, ConfigValue<RegexRuleType>> mainRegexRuleMap =
new HashMap<>();
private final Map<String, Pattern> patternsByRegex = new HashMap<>();
private final Set<ConfigPath> processedConfigs = new HashSet<>();
private final Map<SchemaAndTable, ConfigValue<RegexRuleType>> regexRuleMap =
new HashMap<>();
/**
* Constructor.
*/
public ConfigurationContainer() {
try {
jaxbContext = JAXBContext.newInstance(LQMGType.class.getPackage().getName(), this.getClass()
.getClassLoader());
} catch (JAXBException e) {
throw new LQMGException("Could not create JAXBContext for configuration", e);
}
}
/**
* Adds a configuration.
*/
public void addConfiguration(final ConfigPath configPath) {
if (!processedConfigs.add(configPath)) {
// If the config file is already processed, just return
return;
}
Bundle bundle = configPath.bundle;
String resource = configPath.resource;
URL configurationURL;
if (bundle == null) {
try {
configurationURL = new File(configPath.resource).toURI().toURL();
} catch (MalformedURLException e) {
throw new LQMGException(
"Could not read configuration from path " + configPath.resource, e);
}
} else {
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
ClassLoader classLoader = bundleWiring.getClassLoader();
configurationURL = classLoader.getResource(resource);
}
if (configurationURL == null) {
throw new LQMGException(
"Configuration file not found on the specified path '" + resource + "' in bundle "
+ bundle.toString(),
null);
}
LQMGType lqmgType = unmarshalConfiguration(configurationURL);
processLQMGType(lqmgType, resource, bundle);
}
private <T extends AbstractNamingRuleType> void addValueToConfigMap(
final SchemaAndTable schemaAndTable,
final ConfigValue<T> configValue,
final Map<SchemaAndTable, ConfigValue<T>> configMap) {
ConfigValue<? extends AbstractNamingRuleType> cachedValue = cache.get(schemaAndTable);
if (cachedValue != null) {
cache.remove(schemaAndTable);
}
ConfigValue<T> existingValue = configMap.get(schemaAndTable);
if (existingValue != null) {
StringBuilder sb = new StringBuilder("Configuration is defined more than once: ").append(
schemaAndTable.toString()).append("\n");
Bundle bundle = configValue.bundle;
if (bundle != null) {
sb.append(" Bundle: ").append(bundle.toString()).append("; ");
}
sb.append("Path: ").append(configValue.configurationXMLPath).append("\n");
Bundle existingValueBundle = existingValue.bundle;
if (existingValueBundle != null) {
sb.append(" Bundle: ").append(existingValueBundle.toString()).append("; ");
}
sb.append("Path: ").append(existingValue.configurationXMLPath);
throw new LQMGException(sb.toString(), null);
}
configMap.put(schemaAndTable, configValue);
}
/**
* Finds a {@link ConfigValue} based on schemaName and entityName.
*/
public ConfigValue<? extends AbstractNamingRuleType> findConfigForEntity(
final SchemaAndTable schemaAndTable) {
ConfigValue<? extends AbstractNamingRuleType> configValue = cache.get(schemaAndTable);
if (configValue != null) {
if (configValue instanceof NullConfigValue) {
return null;
} else {
return configValue;
}
}
configValue = findEntityConfigInMap(schemaAndTable, mainClassNameRuleMap);
if (configValue != null) {
return configValue;
}
configValue = findRegexRuleInMap(schemaAndTable, mainRegexRuleMap);
if (configValue != null) {
return configValue;
}
configValue = findEntityConfigInMap(schemaAndTable, classNameRuleMap);
if (configValue != null) {
return configValue;
}
return findRegexRuleInMap(schemaAndTable, regexRuleMap);
}
private ConfigValue<ClassNameRuleType> findEntityConfigInMap(final SchemaAndTable schemaAndTable,
final Map<SchemaAndTable, ConfigValue<ClassNameRuleType>> map) {
ConfigValue<ClassNameRuleType> configValue = map.get(schemaAndTable);
// No schema matched record, trying to search on records where schema is not defined
if (configValue == null) {
SchemaAndTable noSchemaAndTable = new SchemaAndTable(null, schemaAndTable.getTable());
configValue = map.get(noSchemaAndTable);
}
return configValue;
}
private ConfigValue<RegexRuleType> findRegexRuleInMap(final SchemaAndTable schemaAndTable,
final Map<SchemaAndTable, ConfigValue<RegexRuleType>> map) {
List<ConfigValue<RegexRuleType>> result = new ArrayList<ConfigValue<RegexRuleType>>();
String schemaName = schemaAndTable.getSchema();
String tableName = schemaAndTable.getTable();
if (schemaName != null) {
for (Entry<SchemaAndTable, ConfigValue<RegexRuleType>> entry : map.entrySet()) {
SchemaAndTable entryKey = entry.getKey();
ConfigValue<RegexRuleType> configValue = entry.getValue();
String regex = configValue.namingRule.getRegex();
if (schemaName.equals(entryKey.getSchema()) && matches(regex, tableName)) {
result.add(configValue);
}
}
}
validateConfigValueResultSize(schemaAndTable, result);
if (result.size() == 1) {
return result.get(0);
}
// No schema matched record, trying to search on records where schema is not defined
for (Entry<SchemaAndTable, ConfigValue<RegexRuleType>> entry : map.entrySet()) {
ConfigValue<RegexRuleType> configValue = entry.getValue();
RegexRuleType regexRule = configValue.namingRule;
if ((regexRule.getSchema() == null) && matches(regexRule.getRegex(), tableName)) {
result.add(configValue);
}
}
validateConfigValueResultSize(schemaAndTable, result);
if (result.size() == 1) {
return result.get(0);
}
return null;
}
/**
* Returns the cached and compiled {@link Pattern} by the given regex.
*/
public Pattern getPatternByRegex(final String regex) {
Pattern pattern = patternsByRegex.get(regex);
if (pattern == null) {
pattern = Pattern.compile(regex);
patternsByRegex.put(regex, pattern);
}
return pattern;
}
private boolean matches(final String regex, final String value) {
Pattern pattern = getPatternByRegex(regex);
Matcher matcher = pattern.matcher(value);
return matcher.matches();
}
private void processClassNameRuleType(final String xmlConfigurationPath, final Bundle bundle,
final AbstractNamingRuleType lqmgAbstractEntity) {
ClassNameRuleType lqmgEntity = (ClassNameRuleType) lqmgAbstractEntity;
SchemaAndTable schemaAndTable = new SchemaAndTable(
lqmgEntity.getSchema(), lqmgEntity.getEntity());
ConfigValue<ClassNameRuleType> configValue =
new ConfigValue<ClassNameRuleType>(lqmgEntity,
bundle, xmlConfigurationPath);
validatePackage(configValue);
if (bundle == null) {
addValueToConfigMap(schemaAndTable, configValue, mainClassNameRuleMap);
} else {
addValueToConfigMap(schemaAndTable, configValue, classNameRuleMap);
}
}
private void processLQMGType(final LQMGType lqmgType, final String xmlConfigurationPath,
final Bundle bundle) {
String defaultPackageName = lqmgType.getDefaultPackage();
String defaultSchemaName = lqmgType.getDefaultSchema();
NamingRulesType entities = lqmgType.getNamingRules();
if (entities == null) {
return;
}
List<AbstractNamingRuleType> entityAndEntitySet = entities.getClassNameRuleAndRegexRule();
for (AbstractNamingRuleType lqmgAbstractEntity : entityAndEntitySet) {
if (lqmgAbstractEntity.getPackage() == null) {
lqmgAbstractEntity.setPackage(defaultPackageName);
}
if (lqmgAbstractEntity.getSchema() == null) {
lqmgAbstractEntity.setSchema(defaultSchemaName);
}
if (lqmgAbstractEntity.getPrefix() == null) {
lqmgAbstractEntity.setPrefix(lqmgType.getDefaultPrefix());
}
if (lqmgAbstractEntity.getSuffix() == null) {
lqmgAbstractEntity.setSuffix(lqmgType.getDefaultSuffix());
}
if (lqmgAbstractEntity instanceof ClassNameRuleType) {
processClassNameRuleType(xmlConfigurationPath, bundle, lqmgAbstractEntity);
} else if (lqmgAbstractEntity instanceof RegexRuleType) {
processRegexRuleType(xmlConfigurationPath, bundle, lqmgAbstractEntity);
} else {
throw new IllegalArgumentException(
"Unsupported naming rule type [" + lqmgAbstractEntity + "]");
}
}
}
private void processRegexRuleType(final String xmlConfigurationPath, final Bundle bundle,
final AbstractNamingRuleType lqmgAbstractEntity) {
RegexRuleType lqmgEntitySet = (RegexRuleType) lqmgAbstractEntity;
SchemaAndTable configKey = new SchemaAndTable(
lqmgEntitySet.getSchema(), lqmgEntitySet.getRegex());
ConfigValue<RegexRuleType> configValue = new ConfigValue<RegexRuleType>(lqmgEntitySet,
bundle, xmlConfigurationPath);
validatePackage(configValue);
if (bundle == null) {
addValueToConfigMap(configKey, configValue, mainRegexRuleMap);
} else {
addValueToConfigMap(configKey, configValue, regexRuleMap);
}
}
private void throwMultipleMatchingRegexException(final SchemaAndTable schemaAndTable,
final List<ConfigValue<RegexRuleType>> matchingConfigs) {
StringBuilder sb = new StringBuilder("Cannot decide which configuration should be applied to '")
.append(schemaAndTable.getTable()).append("' table of '").append(schemaAndTable.getSchema())
.append("' schema. Found matchings:\n");
for (ConfigValue<RegexRuleType> configValue : matchingConfigs) {
RegexRuleType namingRule = configValue.namingRule;
sb.append(" Bundle: ").append(configValue.bundle).append("; XMLPath: ")
.append(configValue.configurationXMLPath).append("; Schema: ")
.append(namingRule.getSchema())
.append("; Regex: ").append(namingRule.getRegex());
}
throw new LQMGException(sb.toString(), null);
}
private LQMGType unmarshalConfiguration(final URL configurationURL) {
Unmarshaller unmarshaller;
try {
unmarshaller = jaxbContext.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<LQMGType> rootElement =
(JAXBElement<LQMGType>) unmarshaller.unmarshal(configurationURL);
return rootElement.getValue();
} catch (JAXBException e) {
throw new LQMGException(
"Could not unmarshal LQMG configuration: " + configurationURL.toExternalForm(), e);
}
}
private void validateConfigValueResultSize(final SchemaAndTable schemaAndTable,
final List<ConfigValue<RegexRuleType>> result) {
if (result.size() > 1) {
throwMultipleMatchingRegexException(schemaAndTable, result);
}
}
private void validatePackage(final ConfigValue<? extends AbstractNamingRuleType> configValue) {
AbstractNamingRuleType namingRule = configValue.namingRule;
if ((namingRule.getPackage() != null) && !"".equals(namingRule.getPackage().trim())) {
return;
}
StringBuilder sb = new StringBuilder("Missing java package: ConfigValue [bundle=")
.append(configValue.bundle).append(", configurationXMLPath=")
.append(configValue.configurationXMLPath).append(", namingRule.schema=")
.append(namingRule.getSchema()).append(", ");
if (namingRule instanceof RegexRuleType) {
sb.append("namingRule.regex=").append(((RegexRuleType) namingRule).getRegex());
} else if (namingRule instanceof ClassNameRuleType) {
sb.append("namingRule.class=").append(((ClassNameRuleType) namingRule).getClazz());
}
sb.append("].");
throw new LQMGException(sb.toString(), null);
}
}