/** * Copyright (C) 2015 Orange * 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 com.francetelecom.clara.cloud.archive; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import com.francetelecom.clara.cloud.commons.MavenReference; import com.francetelecom.clara.cloud.commons.TechnicalException; import freemarker.template.Configuration; import freemarker.template.TemplateException; /** * Manage ear files or other archives using java 7 nio library * * @author BEAL6226 * */ public class ManageArchiveImpl implements ManageArchive { private static Logger logger = LoggerFactory.getLogger(ManageArchiveImpl.class); /** * Directory in classpath for source files to put in war */ public static final String TEMPLATE_WAR_DIR = "/paas-archive-templates-war/"; /** * Directory in classpath for source files to put in ear */ public static final String TEMPLATE_EAR_DIR = "/paas-archive-templates-ear/"; /** * freemarker configuration */ protected Configuration configuration; public ManageArchiveImpl() { super(); } /* * (non-Javadoc) * * @see * com.francetelecom.clara.cloud.archive.ManageArchive#generateMinimalEar(com.francetelecom.clara.cloud.commons.MavenReference, * java.lang.String) */ @Override public File generateMinimalEar(MavenReference mavenReference, String contextRoot) throws TechnicalException { // Prepare war and ear filename String earFilename = mavenReference.getArtifactName(); if (!earFilename.endsWith(".ear")) { logger.error("provided Maven reference {} is not an ear", mavenReference); throw new TechnicalException("provided Maven reference is not an ear"); } String warFilename = earFilename.replaceFirst(".ear$", ".war"); // Prepare temp directory to store war and ear Path tempDir; try { tempDir = Files.createTempDirectory("ear"); } catch (IOException e) { throw new TechnicalException("cannot create empty directory", e); } URI earUri = generateJarUri(tempDir, earFilename); logger.info("URI of ear to be generated : {}", earUri.toString()); // Prepare map for Freemarker (the same map is used for all templates) Map<String, Object> freemarkerModel = createFreemarkerModel(mavenReference, contextRoot); Path warFile = generateWar(warFilename, tempDir, freemarkerModel); // build ear file with war file and files in TEMPLATE_EAR_DIR try (FileSystem earFileSystem = FileSystems.newFileSystem(earUri, getFileSystemEnv())) { // earFileSystem is automatically closed addAbsoluteFileToJarFile(warFile.getParent(), warFile, earFileSystem); // addSourceFilesToJarFile(TEMPLATE_EAR_DIR, freemarkerModel, earFileSystem); createDirectoryInJarFile("META-INF", earFileSystem); addClasspathTemplateToJarFile(TEMPLATE_EAR_DIR, "META-INF/application.xml.flt", freemarkerModel, earFileSystem); } catch (IOException e) { throw new TechnicalException("cannot create jar filesystem", e); } // delete war file as it is no more useful try { Files.delete(warFile); } catch (IOException e) { logger.debug("cannot delete war file {}", warFile, e); } return new File(tempDir.toString(), earFilename); } @Override public File generateMinimalWar(MavenReference mavenReferenceForWarGeneration, String contextRoot) throws TechnicalException { String warFilename = mavenReferenceForWarGeneration.getArtifactName(); if (!warFilename.endsWith(".war")) { logger.warn("provided Maven reference {} is not an war", mavenReferenceForWarGeneration); warFilename=warFilename.substring(0,warFilename.length()-4) + ".war"; logger.warn("renamed {} to {} for maven reference {}", mavenReferenceForWarGeneration.getArtifactName(),warFilename, mavenReferenceForWarGeneration); } // Prepare temp directory to store war and ear Path tempDir; try { tempDir = Files.createTempDirectory("war"); } catch (IOException e) { throw new TechnicalException("cannot create empty directory", e); } URI warUri = generateJarUri(tempDir, warFilename); logger.info("URI of ear to be generated : {}", warUri.toString()); // Prepare map for Freemarker (the same map is used for all templates) Map<String, Object> freemarkerModel = createFreemarkerModel(mavenReferenceForWarGeneration, contextRoot); Path warFile = generateWar(warFilename, tempDir, freemarkerModel); return new File(warFile.toString()); } private Path generateWar(String warFilename, Path tempDir, Map<String, Object> freemarkerModel) { URI warUri = generateJarUri(tempDir, warFilename); // build war file with files in TEMPLATE_WAR_DIR try (FileSystem warFileSystem = FileSystems.newFileSystem(warUri, getFileSystemEnv())) { // warFileSystem is automatically closed // Not so easy to list all files in TEMPLATE_WAR_DIR, because on production environment we're inside a jar, // thus we cannot use NIO Files.walkFileTree() nor File.listFiles() // So for the moment we just add all files in war manually addClasspathTemplateToJarFile(TEMPLATE_WAR_DIR, "index.html.flt", freemarkerModel, warFileSystem); createDirectoryInJarFile("WEB-INF", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "WEB-INF/web.xml", warFileSystem); createDirectoryInJarFile("styles", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "styles/application.css", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "styles/footer.css", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "styles/gabarits.css", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "styles/orange-main.css", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "styles/signin.css", warFileSystem); createDirectoryInJarFile("images", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "images/favicon.ico", warFileSystem); addClasspathFileToJarFile(TEMPLATE_WAR_DIR, "images/orange_logo.jpg", warFileSystem); } catch (IOException e) { throw new TechnicalException("cannot create war filesystem", e); } return Paths.get(tempDir.toString(), warFilename); } private Map<String, String> getFileSystemEnv() { // Prepare map for FileSystem creation Map<String, String> env = new HashMap<>(); env.put("create", "true"); return env; } /** * create the freemarker model with all ${} variables and values * * @param mavenReference * the ear Maven reference, used to define war and ear file names * @param contextRoot * the war context root * @return the generated model */ protected Map<String, Object> createFreemarkerModel(MavenReference mavenReference, String contextRoot) { Map<String, Object> model = new HashMap<String, Object>(); model.put("warFilename", mavenReference.getArtifactName().replaceFirst(".ear$", ".war")); model.put("contextRoot", contextRoot); model.put("groupid", mavenReference.getGroupId()); model.put("artifactid", mavenReference.getArtifactId()); model.put("version", mavenReference.getVersion()); model.put("classifier", mavenReference.getClassifier()); model.put("extension", mavenReference.getExtension()); model.put("buildDate", Calendar.getInstance().getTime()); return model; } /** * generate a jar URI by using the syntax defined in java.net.JarURLConnection for example * jar:file:/tmp/ear11111111/myappli-1.0.0-SNAPSHOT.ear */ protected URI generateJarUri(Path directory, String jarFile) { StringBuilder uriString = new StringBuilder("jar:"); uriString.append(directory.toUri().toString()); uriString.append(jarFile); return URI.create(uriString.toString()); } /** * Add a file from classpath to a jar filesystem * @param templateDir the directory containing file * @param filename the relative path to the file to be added * @param jarFileSystem the jar file system (war/ear) */ protected void addClasspathFileToJarFile(String templateDir, String filename, FileSystem jarFileSystem) { InputStream inputStream = getClass().getResourceAsStream(templateDir + filename); if (inputStream == null) { logger.debug("file : {}{} not found in classpath", templateDir, filename); throw new TechnicalException("file " + templateDir + filename + " not found in classpath"); } addInputStreamToJarFile(inputStream, filename, jarFileSystem); } /** * Add a generated freemarker template to a jar filesystem * @param templateDir the directory containing template file * @param templateFile the relative path to the template (*.flt) to be added * @param freemarkerModel the freemarker model to be used * @param jarFileSystem the jar file system (war/ear) */ protected void addClasspathTemplateToJarFile(String templateDir, String templateFile, Map<String, Object> freemarkerModel, FileSystem jarFileSystem) { String sourceFile; if (templateFile.endsWith(".flt")) { sourceFile = templateFile.replaceFirst(".flt$", ""); } else { throw new TechnicalException("template file " + templateFile + " do not end with .flt"); } logger.debug("generating {} content with freemarker", templateFile); InputStream inputStream = generateFreemarkerContent(templateFile, freemarkerModel); addInputStreamToJarFile(inputStream, sourceFile, jarFileSystem); } /** * Generate a freemarker content based on a template and a model * @param templateFile the template file * @param freemarkerModel the model * @return an InputStream */ protected InputStream generateFreemarkerContent(String templateFile, Map<String, Object> freemarkerModel) { try { String fileContent = FreeMarkerTemplateUtils.processTemplateIntoString(configuration.getTemplate(templateFile), freemarkerModel); logger.debug("generated content:\n{}", fileContent); InputStream inputStream = new ByteArrayInputStream(fileContent.getBytes()); return inputStream; } catch (IOException | TemplateException e) { throw new TechnicalException("cannot generate template file " + templateFile, e); } } /** * add some content to jar file system * @param inputStream the content to be added * @param filename the filename (relative path) within jar file system * @param jarFileSystem the jar file system (war/ear) */ private void addInputStreamToJarFile(InputStream inputStream, String filename, FileSystem jarFileSystem) { Path internalFile = jarFileSystem.getPath(filename); logger.debug("Adding file {} to {}", internalFile, jarFileSystem); try { Files.copy(inputStream, internalFile); } catch (IOException e) { throw new TechnicalException("cannot copy file " + filename + " in jar file " + jarFileSystem, e); } } /** * Copy a file in jarFileSystem. Path must be valid in filesystem * * @param dir * the directory containing file * @param sourceFile * the file to be copied in jarFileSystem * @param jarFileSystem * the jar file system */ protected void addAbsoluteFileToJarFile(Path dir, Path sourceFile, FileSystem jarFileSystem) { if (!sourceFile.startsWith(dir)) { throw new TechnicalException("source file " + sourceFile + " is not in directory " + dir); } Path internalFile = jarFileSystem.getPath(dir.relativize(sourceFile).toString()); logger.debug("Adding file {} to {}", internalFile, jarFileSystem); try { Files.copy(sourceFile, internalFile); } catch (IOException e) { throw new TechnicalException("cannot copy file " + sourceFile + " in jar file " + jarFileSystem, e); } } /** * Create a directory in jarFileSystem. Path must be valid in fileSystem * * @param sourceDir * the directory to be created in jarFileSystem * @param jarFileSystem * the jar file system */ protected void createDirectoryInJarFile(String sourceDir, FileSystem jarFileSystem) { Path internalDir = jarFileSystem.getPath(sourceDir); try { Files.createDirectory(internalDir); } catch (IOException e) { throw new TechnicalException("cannot create directory " + internalDir + " in jar file " + jarFileSystem, e); } } /** * IOC * * @param configuration */ public void setConfiguration(Configuration configuration) { this.configuration = configuration; } }