001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2015 ForgeRock AS. 025 */ 026package org.forgerock.opendj.maven.doc; 027 028import static org.forgerock.util.Utils.*; 029 030import freemarker.template.Configuration; 031import freemarker.template.Template; 032import freemarker.template.TemplateExceptionHandler; 033import org.apache.maven.artifact.DependencyResolutionRequiredException; 034import org.apache.maven.plugin.logging.Log; 035import org.apache.maven.project.MavenProject; 036 037import java.io.ByteArrayOutputStream; 038import java.io.File; 039import java.io.FileInputStream; 040import java.io.FileOutputStream; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.OutputStream; 044import java.io.OutputStreamWriter; 045import java.io.PrintWriter; 046import java.io.Writer; 047import java.net.MalformedURLException; 048import java.net.URISyntaxException; 049import java.net.URL; 050import java.net.URLClassLoader; 051import java.util.Collections; 052import java.util.LinkedHashSet; 053import java.util.List; 054import java.util.Map; 055import java.util.Set; 056 057/** 058 * Provides utility methods for generating documentation. 059 */ 060public final class Utils { 061 062 /** Line separator. */ 063 static final String EOL = System.getProperty("line.separator"); 064 065 /** 066 * Creates a directory unless it already exists. 067 * @param directory The directory to create. 068 * @throws IOException Failed to create directory. 069 */ 070 static void createDirectory(final String directory) throws IOException { 071 File dir = new File(directory); 072 if (!dir.exists()) { 073 if (!dir.mkdirs()) { 074 throw new IOException("Failed to create directory: " + directory); 075 } 076 } 077 } 078 079 /** 080 * Returns the path to the current Java executable. 081 * @return The path to the current Java executable. 082 */ 083 static String getJavaCommand() { 084 return System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; 085 } 086 087 /** 088 * Copies the content of the original file to the copy. 089 * @param original The original file. 090 * @param copy The copy. 091 * @throws IOException Failed to make the copy. 092 */ 093 static void copyFile(File original, File copy) throws IOException { 094 copyInputStreamToFile(new FileInputStream(original), copy); 095 } 096 097 /** 098 * Copies the content of the original input stream to the copy. 099 * @param original The original input stream. 100 * @param copy The copy. 101 * @throws IOException Failed to make the copy. 102 */ 103 static void copyInputStreamToFile(InputStream original, File copy) throws IOException { 104 if (original == null) { 105 throw new IOException("Could not read input to copy."); 106 } 107 createFile(copy); 108 try (OutputStream outputStream = new FileOutputStream(copy)) { 109 int bytesRead; 110 byte[] buffer = new byte[4096]; 111 while ((bytesRead = original.read(buffer)) > 0) { 112 outputStream.write(buffer, 0, bytesRead); 113 } 114 } finally { 115 closeSilently(original); 116 } 117 } 118 119 /** 120 * Writes a string to a file. 121 * @param string The string to write 122 * @param file The file to write to 123 * @throws IOException The file did not exist, or could not be created for writing. 124 */ 125 static void writeStringToFile(final String string, final File file) throws IOException { 126 createFile(file); 127 PrintWriter printWriter = new PrintWriter(file); 128 printWriter.print(string); 129 printWriter.close(); 130 } 131 132 /** 133 * Creates a file including parent directories if it does not yet exist. 134 * @param file The file to create 135 * @throws IOException Failed to create the file 136 */ 137 private static void createFile(File file) throws IOException { 138 if (!file.exists()) { 139 createDirectory(file.getParent()); 140 if (!file.createNewFile()) { 141 throw new IOException("Failed to create " + file.getPath()); 142 } 143 } 144 } 145 146 /** 147 * Returns the classpath for the class loader and its parent. 148 * @param classLoader Contains the URLs of the class path to return. 149 * @return The classpath for the class loader and its parent. 150 */ 151 static String getClassPath(URLClassLoader classLoader) throws URISyntaxException { 152 Set<URL> urls = new LinkedHashSet<>(); 153 Collections.addAll(urls, classLoader.getURLs()); 154 Collections.addAll(urls, ((URLClassLoader) classLoader.getParent()).getURLs()); 155 Set<String> paths = new LinkedHashSet<>(); 156 for (URL url: urls) { 157 paths.add(new File(url.toURI()).getPath()); 158 } 159 return joinAsString(File.pathSeparator, paths); 160 } 161 162 /** 163 * Returns a ClassLoader including the project's runtime classpath elements. 164 * This is useful when running a Java command from inside a Maven plugin. 165 * 166 * @param project The Maven project holding runtime classpath elements. 167 * @param log A plugin log to use for debugging. 168 * @return A ClassLoader including the project's runtime classpath elements. 169 * @throws DependencyResolutionRequiredException Failed to access the runtime classpath 170 * @throws MalformedURLException Failed to add an element to the classpath 171 */ 172 static URLClassLoader getRuntimeClassLoader(MavenProject project, Log log) 173 throws DependencyResolutionRequiredException, MalformedURLException { 174 List<String> runtimeClasspathElements = project.getRuntimeClasspathElements(); 175 Set<URL> runtimeUrls = new LinkedHashSet<>(); 176 for (String element : runtimeClasspathElements) { 177 runtimeUrls.add(new File(element).toURI().toURL()); 178 } 179 180 final URLClassLoader urlClassLoader = new URLClassLoader( 181 runtimeUrls.toArray(new URL[runtimeClasspathElements.size()]), 182 Thread.currentThread().getContextClassLoader()); 183 debugClassPathElements(urlClassLoader, log); 184 return urlClassLoader; 185 } 186 187 /** 188 * Logs what is on the classpath for debugging. 189 * @param classLoader The ClassLoader with the classpath. 190 * @param log The Maven plugin log in which to write debug messages. 191 */ 192 static void debugClassPathElements(ClassLoader classLoader, Log log) { 193 if (null == classLoader) { 194 return; 195 } 196 log.debug("--------------------"); 197 log.debug(classLoader.toString()); 198 if (classLoader instanceof URLClassLoader) { 199 final URLClassLoader ucl = (URLClassLoader) classLoader; 200 int i = 0; 201 for (URL url : ucl.getURLs()) { 202 log.debug("url[" + (i++) + "]=" + url); 203 } 204 } 205 debugClassPathElements(classLoader.getParent(), log); 206 } 207 208 /** FreeMarker template configuration. */ 209 static Configuration configuration; 210 211 /** 212 * Returns a FreeMarker configuration for applying templates. 213 * @return A FreeMarker configuration for applying templates. 214 */ 215 static Configuration getConfiguration() { 216 if (configuration == null) { 217 configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); 218 configuration.setClassForTemplateLoading(Utils.class, "/templates"); 219 configuration.setDefaultEncoding("UTF-8"); 220 configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); 221 } 222 return configuration; 223 } 224 225 /** 226 * Returns the String result from applying a FreeMarker template. 227 * @param template The name of a template file found in {@code resources/templates/}. 228 * @param map The map holding the data to use in the template. 229 * @return The String result from applying a FreeMarker template. 230 */ 231 static String applyTemplate(final String template, final Map<String, Object> map) { 232 // FreeMarker requires a configuration to find the template. 233 configuration = getConfiguration(); 234 235 // FreeMarker takes the data and a Writer to process the template. 236 try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 237 Writer writer = new OutputStreamWriter(outputStream)) { 238 Template configurationTemplate = configuration.getTemplate(template); 239 configurationTemplate.process(map, writer); 240 return outputStream.toString(); 241 } catch (Exception e) { 242 throw new RuntimeException(e.getMessage(), e); 243 } 244 } 245 246 private Utils() { 247 // Not used. 248 } 249}