package org.springframework.roo.addon.dbre;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.dbre.model.Database;
import org.springframework.roo.addon.dbre.model.DatabaseXmlUtils;
import org.springframework.roo.addon.dbre.model.DbreModelService;
import org.springframework.roo.addon.dbre.model.Schema;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.osgi.OSGiUtils;
import org.springframework.roo.support.util.DomUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Implementation of {@link DbreOperations}.
*
* @author Alan Stewart
* @since 1.1
*/
@Component
@Service
public class DbreOperationsImpl implements DbreOperations {
private static final Logger LOGGER = HandlerUtils
.getLogger(DbreOperationsImpl.class);
@Reference private DbreModelService dbreModelService;
@Reference private FileManager fileManager;
@Reference private PathResolver pathResolver;
@Reference private ProjectOperations projectOperations;
private ComponentContext context;
protected void activate(final ComponentContext context) {
this.context = context;
}
public void displayDatabaseMetadata(final Set<Schema> schemas,
final File file, final boolean view) {
Validate.notNull(schemas, "Schemas required");
// Force it to refresh the database from the actual JDBC connection
final Database database = dbreModelService.refreshDatabase(schemas,
view, Collections.<String> emptySet(),
Collections.<String> emptySet());
database.setIncludeNonPortableAttributes(true);
outputSchemaXml(database, schemas, file, true);
}
protected Collection<URL> findResources(final String path) {
// For an OSGi bundle search, we add the root prefix to the given path
return OSGiUtils.findEntriesByPath(context.getBundleContext(),
OSGiUtils.ROOT_PATH + path);
}
public boolean isDbreInstallationPossible() {
return projectOperations.isFocusedProjectAvailable()
&& projectOperations
.isFeatureInstalledInFocusedModule(FeatureNames.JPA);
}
private void outputSchemaXml(final Database database,
final Set<Schema> schemas, final File file,
final boolean displayOnly) {
if (database == null) {
LOGGER.warning("Cannot obtain database information for schema(s) '"
+ StringUtils.join(schemas, ",") + "'");
}
else if (!database.hasTables()) {
LOGGER.warning("Schema(s) '"
+ StringUtils.join(schemas, ",")
+ "' do not exist or does not have any tables. Note that the schema names of some databases are case-sensitive");
}
else {
try {
if (displayOnly) {
final Document document = DatabaseXmlUtils
.getDatabaseDocument(database);
final OutputStream outputStream = file != null ? new FileOutputStream(
file) : new ByteArrayOutputStream();
XmlUtils.writeXml(outputStream, document);
LOGGER.info(file != null ? "Database metadata written to file "
+ file.getAbsolutePath()
: outputStream.toString());
}
else {
dbreModelService.writeDatabase(database);
}
}
catch (final Exception e) {
throw new IllegalStateException(e);
}
}
}
public void reverseEngineerDatabase(final Set<Schema> schemas,
final JavaPackage destinationPackage,
final boolean testAutomatically, final boolean view,
final Set<String> includeTables, final Set<String> excludeTables,
final boolean includeNonPortableAttributes,
final boolean activeRecord, final File tableNameMapper) {
// Force it to refresh the database from the actual JDBC connection
final Database database = dbreModelService.refreshDatabase(schemas,
view, includeTables, excludeTables);
// Check on tableNameMapper param (File vs String) mcm
DbreTypeUtils.initAliasMappings((tableNameMapper != null) ? tableNameMapper.getPath() : null, database);
database.setModuleName(projectOperations.getFocusedModuleName());
database.setActiveRecord(activeRecord);
database.setDestinationPackage(destinationPackage);
database.setIncludeNonPortableAttributes(includeNonPortableAttributes);
database.setTestAutomatically(testAutomatically);
outputSchemaXml(database, schemas, null, false);
// Update the pom.xml to add an exclusion for the DBRE XML file in the
// maven-war-plugin
updatePom();
// Change the persistence.xml file to prevent tables being created and
// dropped.
updatePersistenceXml();
}
private boolean setPropertyValue(final Element root,
Element propertyElement, final String name, final String value) {
boolean changed = false;
propertyElement = XmlUtils.findFirstElement(
"/persistence/persistence-unit/properties/property[@name = '"
+ name + "']", root);
if (propertyElement != null
&& !propertyElement.getAttribute("value").equals(value)) {
propertyElement.setAttribute("value", value);
changed = true;
}
return changed;
}
private void updatePersistenceXml() {
final String persistencePath = pathResolver.getFocusedIdentifier(
Path.SRC_MAIN_RESOURCES, "META-INF/persistence.xml");
final Document document = XmlUtils.readXml(fileManager
.getInputStream(persistencePath));
final Element root = document.getDocumentElement();
final Element providerElement = XmlUtils
.findFirstElement(
"/persistence/persistence-unit[@transaction-type = 'RESOURCE_LOCAL']/provider",
root);
Validate.notNull(providerElement,
"/persistence/persistence-unit/provider is null");
final String provider = providerElement.getTextContent();
final Element propertyElement = null;
boolean changed = false;
if (provider.contains("hibernate")) {
changed = setPropertyValue(root, propertyElement,
"hibernate.hbm2ddl.auto", "validate");
changed |= setPropertyValue(root, propertyElement,
"hibernate.ejb.naming_strategy",
"org.hibernate.cfg.DefaultNamingStrategy");
}
else if (provider.contains("openjpa")) {
changed = setPropertyValue(root, propertyElement,
"openjpa.jdbc.SynchronizeMappings", "validate");
}
else if (provider.contains("eclipse")) {
changed = setPropertyValue(root, propertyElement,
"eclipselink.ddl-generation", "none");
}
else if (provider.contains("datanucleus")) {
changed = setPropertyValue(root, propertyElement,
"datanucleus.autoCreateSchema", "false");
changed |= setPropertyValue(root, propertyElement,
"datanucleus.autoCreateTables", "false");
changed |= setPropertyValue(root, propertyElement,
"datanucleus.autoCreateColumns", "false");
changed |= setPropertyValue(root, propertyElement,
"datanucleus.autoCreateConstraints", "false");
changed |= setPropertyValue(root, propertyElement,
"datanucleus.validateTables", "false");
changed |= setPropertyValue(root, propertyElement,
"datanucleus.validateConstraints", "false");
}
else {
throw new IllegalStateException("Persistence provider " + provider
+ " is not supported");
}
if (changed) {
fileManager.createOrUpdateTextFileIfRequired(persistencePath,
XmlUtils.nodeToString(document), false);
}
}
private void updatePom() {
final String pom = pathResolver.getFocusedIdentifier(Path.ROOT,
"pom.xml");
final Document document = XmlUtils.readXml(fileManager
.getInputStream(pom));
final Element root = document.getDocumentElement();
final String warPluginXPath = "/project/build/plugins/plugin[artifactId = 'maven-war-plugin']";
final Element warPluginElement = XmlUtils.findFirstElement(
warPluginXPath, root);
if (warPluginElement == null) {
// Project may not be a web project, so just exit
return;
}
Element excludeElement = XmlUtils
.findFirstElement(
warPluginXPath
+ "/configuration/webResources/resource/excludes/exclude[text() = '"
+ DbreModelService.DBRE_XML + "']", root);
if (excludeElement != null) {
// <exclude> element is already there, so just exit
return;
}
// Create the required elements
final Element configurationElement = DomUtils.createChildIfNotExists(
"configuration", warPluginElement, document);
final Element webResourcesElement = DomUtils.createChildIfNotExists(
"webResources", configurationElement, document);
final Element resourceElement = DomUtils.createChildIfNotExists(
"resource", webResourcesElement, document);
final Element excludesElement = DomUtils.createChildIfNotExists(
"excludes", resourceElement, document);
excludeElement = DomUtils.createChildIfNotExists("exclude",
excludesElement, document);
final Element directoryElement = DomUtils.createChildIfNotExists(
"directory", resourceElement, document);
// Populate them with the required text
excludeElement.setTextContent(DbreModelService.DBRE_XML);
directoryElement.setTextContent("src/main/resources");
// Clean up the XML
DomUtils.removeTextNodes(warPluginElement);
// Write out the updated POM
fileManager.createOrUpdateTextFileIfRequired(pom,
XmlUtils.nodeToString(document), false);
}
}