/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.gvnix.web.report.roo.addon.addon; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.io.IOUtils; 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.gvnix.support.dependenciesmanager.DependenciesVersionManager; import org.osgi.service.component.ComponentContext; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.project.Dependency; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; import org.springframework.roo.project.Plugin; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.project.Repository; import org.springframework.roo.support.osgi.OSGiUtils; import org.springframework.roo.support.util.FileUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * Service offering some configuration operations * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> * @since 0.7 */ @Component @Service public class ReportConfigServiceImpl implements ReportConfigService { private static final String JASPER_VIEWS_XML = "WEB-INF/spring/jasper-views.xml"; @Reference private FileManager fileManager; @Reference private ProjectOperations projectOperations; @Reference private MetadataService metadataService; private ComponentContext context; /** webmvc-config.xml path */ private String springMvcConfigFile; /** jasper-views.xml path */ private String jasperViewsConfigFile; /** {@inheritDoc} */ public void setup() { Validate.isTrue(isSpringMvcProject(), "Project must be Spring MVC project"); Element configuration = XmlUtils.getConfiguration(getClass()); // Add addon repository and dependency to get annotations addAnnotations(configuration); // Add properties to pom updatePomProperties(configuration); // Add JasperReports dependencies to pom updateDependencies(configuration); // Add JasperReports plugin to pom // TODO: The jasperreports-maven-plugin doesn't support the current // version of JasperReports lib. // updatePlugins(configuration); // Add JasperReports View resolver to webmvc-config.xml and create the // jasper-views.xml config file. addJasperReportsViewResolver(); // Install font family extension in the project installJasperReportsExtensionFonts(); } /** * Add the ViewResolver config to webmvc-config.xml */ public final void addJasperReportsViewResolver() { PathResolver pathResolver = projectOperations.getPathResolver(); // Add config to MVC app context String mvcConfig = pathResolver.getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "WEB-INF/spring/webmvc-config.xml"); MutableFile mutableMvcConfigFile = fileManager.updateFile(mvcConfig); Document mvcConfigDocument; try { mvcConfigDocument = XmlUtils.getDocumentBuilder().parse( mutableMvcConfigFile.getInputStream()); } catch (Exception ex) { throw new IllegalStateException( "Could not open Spring MVC config file '" + mvcConfig + "'", ex); } Element beans = mvcConfigDocument.getDocumentElement(); if (null == XmlUtils.findFirstElement( "/beans/bean[@id='jasperReportsXmlViewResolver']", beans)) { InputStream configTemplateInputStream = null; OutputStream mutableMvcConfigFileOutput = null; try { configTemplateInputStream = FileUtils.getInputStream( getClass(), "jasperreports-mvc-config-template.xml"); Validate.notNull(configTemplateInputStream, "Could not acquire jasperreports-mvc-config-template.xml file"); Document configDoc; try { configDoc = XmlUtils.getDocumentBuilder().parse( configTemplateInputStream); } catch (Exception e) { throw new IllegalStateException(e); } Element configElement = (Element) configDoc.getFirstChild(); Element jasperReportsBeanConfig = XmlUtils.findFirstElement( "/config/bean", configElement); Node importedElement = mvcConfigDocument.importNode( jasperReportsBeanConfig, true); beans.appendChild(importedElement); mutableMvcConfigFileOutput = mutableMvcConfigFile .getOutputStream(); XmlUtils.writeXml(mutableMvcConfigFileOutput, mvcConfigDocument); } finally { IOUtils.closeQuietly(mutableMvcConfigFileOutput); IOUtils.closeQuietly(configTemplateInputStream); } } if (fileManager.exists(pathResolver.getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), JASPER_VIEWS_XML))) { fileManager.scan(); // This file already exists, nothing to do return; } // Add jasper-views.xml file MutableFile mutableFile; InputStream jRepTInStrm; try { jRepTInStrm = FileUtils.getInputStream(getClass(), "jasperreports-views-config-template.xml"); } catch (Exception ex) { throw new IllegalStateException( "Unable to load jasperreports-views-config-template.xml", ex); } String jasperViesFileDestination = pathResolver.getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), JASPER_VIEWS_XML); if (!fileManager.exists(jasperViesFileDestination)) { mutableFile = fileManager.createFile(jasperViesFileDestination); Validate.notNull(mutableFile, "Could not create JasperReports views definition file '" .concat(jasperViesFileDestination).concat("'")); } else { mutableFile = fileManager.updateFile(jasperViesFileDestination); } InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = jRepTInStrm; outputStream = mutableFile.getOutputStream(); IOUtils.copy(inputStream, outputStream); } catch (IOException ioe) { throw new IllegalStateException("Could not output '".concat( mutableFile.getCanonicalPath()).concat("'"), ioe); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } fileManager.scan(); } /** {@inheritDoc} */ public boolean isJasperViewsProject() { return fileManager.exists(getJasperViewsFile()); } /** {@inheritDoc} */ public boolean isSpringMvcProject() { return fileManager.exists(getSpringMvcConfigFile()); } /** * {@inheritDoc} * <p> * Do not permit ad reports unless they have a web project with Spring MVC * Tiles. */ public boolean isSpringMvcTilesProject() { return fileManager.exists(getSpringMvcConfigFile()) && fileManager.exists(getTilesLayoutsFile()); } /** {@inheritDoc} */ public String getSpringMvcConfigFile() { // resolve path for spring-mvc.xml if it hasn't been resolved yet if (springMvcConfigFile == null) { springMvcConfigFile = projectOperations.getPathResolver() .getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "WEB-INF/spring/webmvc-config.xml"); } return springMvcConfigFile; } /** {@inheritDoc} */ public String getJasperViewsFile() { // resolve path for jasper-views.xml if it hasn't been resolved yet if (jasperViewsConfigFile == null) { jasperViewsConfigFile = projectOperations.getPathResolver() .getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), JASPER_VIEWS_XML); } return jasperViewsConfigFile; } protected void activate(ComponentContext context) { this.context = context; } /** * Get the absolute path for {@code layouts.xml}. * <p> * Note that this file is required for any Tiles project. * * @return the absolute path to file (never null) */ private String getTilesLayoutsFile() { // resolve absolute path for menu.jspx if it hasn't been resolved yet return projectOperations.getPathResolver().getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/layouts/layouts.xml"); } /** * Add addon repository and dependency to get annotations. * * @param configuration Configuration element */ private void addAnnotations(Element configuration) { // Install the add-on Google code repository and dependency needed to // get the annotations List<Element> repos = XmlUtils.findElements( "/configuration/gvnix/repositories/repository", configuration); for (Element repo : repos) { projectOperations.addRepository(projectOperations .getFocusedModuleName(), new Repository(repo)); } List<Element> depens = XmlUtils.findElements( "/configuration/gvnix/dependencies/dependency", configuration); DependenciesVersionManager.manageDependencyVersion(metadataService, projectOperations, depens); } /** * Install properties defined in external XML file * * @param configuration */ private void updatePomProperties(Element configuration) { List<Element> addonProperties = XmlUtils.findElements( "/configuration/gvnix/web-report/properties/*", configuration); DependenciesVersionManager.managePropertyVersion(metadataService, projectOperations, addonProperties); } /** * Install dependencies defined in external XML file * * @param configuration */ private void updateDependencies(Element configuration) { List<Dependency> dependencies = new ArrayList<Dependency>(); List<Element> jasperReportDependencies = XmlUtils.findElements( "/configuration/gvnix/jasperReports/dependencies/dependency", configuration); for (Element dependencyElement : jasperReportDependencies) { dependencies.add(new Dependency(dependencyElement)); } projectOperations.addDependencies( projectOperations.getFocusedModuleName(), dependencies); } /** * Install plugins defined in external XML file * * @param configuration */ @SuppressWarnings("unused") private void updatePlugins(Element configuration) { List<Element> jasperReportsPlugins = XmlUtils.findElements( "/configuration/gvnix/jasperReports/plugins/plugin", configuration); for (Element pluginElement : jasperReportsPlugins) { projectOperations.addBuildPlugin(projectOperations .getFocusedModuleName(), new Plugin(pluginElement)); } } /** * Install a font family in the project and the * jasperreport_extension.properties file This extension and the font family * are needed because is the current way to have bold, italic and underline * fonts in the reports output. <a href= * "http://sites.google.com/site/xmedeko/code/misc/jasperreports-pdf-font-mapping" * >jasperreports-pdf-font-mapping</a>. We apply the same solution but * without a jar file, just creating the files in the application classpath. */ private void installJasperReportsExtensionFonts() { PathResolver pathResolver = projectOperations.getPathResolver(); InputStream configTemplateInputStream = FileUtils .getInputStream(getClass(), "jasperfonts-extension/jasperreports_extension-template.properties"); String jRepExtPropT; try { jRepExtPropT = IOUtils.toString(new InputStreamReader( configTemplateInputStream)); } catch (IOException ioe) { throw new IllegalStateException( "Unable load jasperreports_extension-template.properties", ioe); } finally { try { configTemplateInputStream.close(); } catch (IOException e) { throw new IllegalStateException( "Error creating jasperreports_extension.properties in project", e); } } String jasperReportExtensionProp = pathResolver.getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "WEB-INF/classes/jasperreports_extension.properties"); fileManager.createOrUpdateTextFileIfRequired(jasperReportExtensionProp, jRepExtPropT, false); String classesPathDest = projectOperations.getPathResolver() .getIdentifier( LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "WEB-INF/classes/jasperfonts"); copyDirectoryContents("jasperfonts-extension/jasperfonts/**", classesPathDest); } /** * This method will copy the contents of a directory to another if the * resource does not already exist in the target directory * * @param sourceAntPath the source path * @param targetDirectory the target directory */ private void copyDirectoryContents(String sourceAntPath, String targetDirectory) { Validate.notNull(sourceAntPath, "sourceAntPath required"); Validate.notBlank(sourceAntPath, "sourceAntPath required"); Validate.notNull(targetDirectory, "targetDirectory required"); Validate.notBlank(targetDirectory, "targetDirectory required"); if (!targetDirectory.endsWith("/")) { targetDirectory = targetDirectory.concat("/"); } if (!fileManager.exists(targetDirectory)) { fileManager.createDirectory(targetDirectory); } String path = FileUtils.getPath(getClass(), sourceAntPath); Collection<URL> urls = OSGiUtils.findEntriesByPattern( context.getBundleContext(), path); Validate.notNull( urls, "Could not search bundles for resources for Ant Path '".concat( path).concat("'")); for (URL url : urls) { String fileName = url.getPath().substring( url.getPath().lastIndexOf('/') + 1); String filePath = targetDirectory.concat(fileName); if (!fileManager.exists(filePath)) { try { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = url.openStream(); outputStream = fileManager.createFile(filePath) .getOutputStream(); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } catch (IOException e) { throw new IllegalStateException( "Encountered an error during copying of resources for MVC JSP addon.", e); } } } } }