001/* 002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 */ 017 018import java.io.BufferedInputStream; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.util.Map; 026import java.util.zip.ZipEntry; 027import java.util.zip.ZipFile; 028import java.util.zip.ZipInputStream; 029 030import javax.xml.parsers.DocumentBuilder; 031import javax.xml.parsers.DocumentBuilderFactory; 032import javax.xml.parsers.ParserConfigurationException; 033 034import org.w3c.dom.Document; 035import org.w3c.dom.Element; 036import org.w3c.dom.Node; 037import org.xml.sax.SAXException; 038import org.nuxeo.runtime.api.Framework; 039 040/** 041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 042 */ 043public class Archetype { 044 045 private static final int BUFFER_SIZE = 1024 * 64; // 64K 046 047 private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K 048 049 private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K 050 051 static boolean batchMode = false; 052 053 static String outDir = "${artifactId}"; 054 055 static File archive; 056 057 public static void main(String[] args) throws Exception { 058 if (args.length == 0) { 059 System.err.println("Syntax Error: you must specify a project template name"); 060 } 061 int k = 0; 062 String tpl = args[k]; 063 if ("-b".equals(tpl)) { 064 batchMode = true; 065 if (args.length < 2) { 066 System.err.println("Syntax Error: you must specify a project template name"); 067 } 068 tpl = args[++k]; 069 } 070 k++; 071 archive = new File(tpl); 072 if (args.length > k) { 073 outDir = args[k]; 074 } 075 ZipFile zip = new ZipFile(archive); 076 ZipEntry entry = zip.getEntry("archetype.xml"); 077 if (entry == null) { 078 System.err.println("Invalid archetype zip."); 079 System.exit(1); 080 } 081 // load archetype definition 082 InputStream in = new BufferedInputStream(zip.getInputStream(entry)); 083 Document doc = load(in); 084 zip.close(); 085 // process it 086 processArchetype(doc, System.getProperties()); 087 } 088 089 private static void expandVars(File file, Map<?, ?> vars) throws IOException { 090 String content = readFile(file); 091 content = expandVars(content, vars); 092 writeFile(file, content); 093 } 094 095 public static void unzip(File zip, File dir) throws IOException { 096 ZipInputStream in = null; 097 try { 098 in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip))); 099 unzip(in, dir); 100 } finally { 101 if (in != null) { 102 in.close(); 103 } 104 } 105 } 106 107 public static void unzip(ZipInputStream in, File dir) throws IOException { 108 dir.mkdirs(); 109 ZipEntry entry = in.getNextEntry(); 110 while (entry != null) { 111 // System.out.println("Extracting "+entry.getName()); 112 File file = new File(dir, entry.getName()); 113 if (entry.isDirectory()) { 114 file.mkdirs(); 115 } else { 116 file.getParentFile().mkdirs(); 117 copyToFile(in, file); 118 } 119 entry = in.getNextEntry(); 120 } 121 } 122 123 public static String getShortName(String name) { 124 int p = name.lastIndexOf('.'); 125 if (p > -1) { 126 return name.substring(0, p); 127 } 128 return name; 129 } 130 131 public static String expandVars(String expression, Map<?, ?> properties) { 132 int p = expression.indexOf("${"); 133 if (p == -1) { 134 return expression; // do not expand if not needed 135 } 136 137 char[] buf = expression.toCharArray(); 138 StringBuilder result = new StringBuilder(buf.length); 139 if (p > 0) { 140 result.append(expression.substring(0, p)); 141 } 142 StringBuilder varBuf = new StringBuilder(); 143 boolean dollar = false; 144 boolean var = false; 145 for (int i = p; i < buf.length; i++) { 146 char c = buf[i]; 147 switch (c) { 148 case '$': 149 dollar = true; 150 break; 151 case '{': 152 if (dollar) { 153 dollar = false; 154 var = true; 155 } else { 156 result.append(c); 157 } 158 break; 159 case '}': 160 if (var) { 161 var = false; 162 String varName = varBuf.toString(); 163 varBuf.setLength(0); 164 // get the variable value 165 Object varValue = properties.get(varName); 166 if (varValue != null) { 167 result.append(varValue.toString()); 168 } else { // let the variable as is 169 result.append("${").append(varName).append('}'); 170 } 171 } else { 172 result.append(c); 173 } 174 break; 175 default: 176 if (var) { 177 varBuf.append(c); 178 } else { 179 result.append(c); 180 } 181 break; 182 } 183 } 184 return result.toString(); 185 } 186 187 public static String readFile(File file) throws IOException { 188 FileInputStream in = null; 189 try { 190 in = new FileInputStream(file); 191 return read(in); 192 } finally { 193 if (in != null) { 194 in.close(); 195 } 196 } 197 } 198 199 public static String read(InputStream in) throws IOException { 200 StringBuilder sb = new StringBuilder(); 201 byte[] buffer = createBuffer(in.available()); 202 try { 203 int read; 204 while ((read = in.read(buffer)) != -1) { 205 sb.append(new String(buffer, 0, read)); 206 } 207 } finally { 208 in.close(); 209 } 210 return sb.toString(); 211 } 212 213 public static void copyToFile(InputStream in, File file) throws IOException { 214 OutputStream out = null; 215 try { 216 out = new FileOutputStream(file); 217 byte[] buffer = createBuffer(in.available()); 218 int read; 219 while ((read = in.read(buffer)) != -1) { 220 out.write(buffer, 0, read); 221 } 222 } finally { 223 if (out != null) { 224 out.close(); 225 } 226 } 227 } 228 229 private static byte[] createBuffer(int preferredSize) { 230 if (preferredSize < 1) { 231 preferredSize = BUFFER_SIZE; 232 } 233 if (preferredSize > MAX_BUFFER_SIZE) { 234 preferredSize = MAX_BUFFER_SIZE; 235 } else if (preferredSize < MIN_BUFFER_SIZE) { 236 preferredSize = MIN_BUFFER_SIZE; 237 } 238 return new byte[preferredSize]; 239 } 240 241 public static Document load(File file) throws ParserConfigurationException, SAXException, IOException { 242 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 243 factory.setNamespaceAware(true); 244 DocumentBuilder builder = factory.newDocumentBuilder(); 245 return builder.parse(file); 246 } 247 248 public static Document load(InputStream in) throws ParserConfigurationException, SAXException, IOException { 249 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 250 factory.setNamespaceAware(true); 251 DocumentBuilder builder = factory.newDocumentBuilder(); 252 return builder.parse(in); 253 } 254 255 public static void processVars(Element root, Map<Object, Object> vars) throws IOException { 256 Node node = root.getFirstChild(); 257 while (node != null) { 258 if (node.getNodeType() == Node.ELEMENT_NODE) { 259 Element el = (Element) node; 260 if ("var".equals(el.getNodeName())) { 261 String key = el.getAttribute("name"); 262 String label = el.getAttribute("label"); 263 if (label == null) { 264 label = key; 265 } 266 String val = (String) vars.get(key); 267 String def = el.getAttribute("default"); 268 if (def != null) { 269 def = expandVars(def, vars); 270 } else { 271 def = val; 272 } 273 if (!batchMode && val == null && "true".equals(el.getAttribute("prompt"))) { 274 val = readVar(label, def); 275 } 276 if (val == null) { 277 val = def; 278 } 279 vars.put(key, val); 280 } 281 } 282 node = node.getNextSibling(); 283 } 284 } 285 286 public static void processResources(Element root, File dir, Map<Object, Object> vars) throws IOException { 287 Node node = root.getFirstChild(); 288 while (node != null) { 289 if (node.getNodeType() == Node.ELEMENT_NODE) { 290 Element el = (Element) node; 291 if ("directory".equals(el.getNodeName())) { 292 String srcName = el.getAttribute("src"); 293 if (srcName == null) { 294 throw new IllegalArgumentException("directory has no src attribute"); 295 } 296 String targetName = el.getAttribute("target"); 297 if (targetName == null) { 298 throw new IllegalArgumentException("directory has no target attribute"); 299 } 300 srcName = expandVars(srcName, vars); 301 targetName = expandVars(targetName, vars); 302 File src = new File(dir, srcName); 303 File dst = new File(dir, targetName); 304 System.out.println("Renaming " + src + " to " + dst); 305 src.renameTo(dst); 306 } else if ("package".equals(el.getNodeName())) { 307 String srcName = el.getAttribute("src"); 308 if (srcName == null) { 309 throw new IllegalArgumentException("package has no src attribute"); 310 } 311 String targetName = el.getAttribute("target"); 312 if (targetName == null) { 313 throw new IllegalArgumentException("package has no target attribute"); 314 } 315 srcName = expandVars(srcName, vars); 316 targetName = expandVars(targetName, vars); 317 targetName = targetName.replaceAll("\\.", "/"); 318 File src = new File(dir, srcName); 319 File dst = new File(dir, targetName); 320 System.out.println("Renaming " + src + " to " + dst); 321 dst.getParentFile().mkdirs(); 322 src.renameTo(dst); 323 } else if ("template".equals(el.getNodeName())) { 324 String srcName = el.getAttribute("src"); 325 if (srcName == null) { 326 throw new IllegalArgumentException("rename has no src attribute"); 327 } 328 File src = new File(dir, srcName); 329 System.out.println("Processing " + src); 330 expandVars(src, vars); 331 } 332 } 333 node = node.getNextSibling(); 334 } 335 } 336 337 public static void processArchetype(Document doc, Map<Object, Object> vars) throws IOException { 338 Element root = doc.getDocumentElement(); 339 Node node = root.getFirstChild(); 340 Element elVars = null; 341 Element elRes = null; 342 while (node != null) { 343 if (node.getNodeType() == Node.ELEMENT_NODE) { 344 Element el = (Element) node; 345 if ("vars".equals(el.getNodeName())) { 346 elVars = el; 347 } else if ("resources".equals(el.getNodeName())) { 348 elRes = el; 349 } 350 } 351 node = node.getNextSibling(); 352 } 353 if (elVars != null) { 354 processVars(elVars, vars); 355 } 356 // System.out.println("vars: "+System.getProperty("artifactId")+" - "+System.getProperty("groupId")+" = "+System.getProperty("moduleId")); 357 outDir = expandVars(outDir, vars); 358 File out = new File(outDir); 359 if (out.exists()) { 360 System.out.println("Target directory already exists: " + out); 361 System.out.println("Please specify as target a directory to be created. Exiting."); 362 System.exit(1); 363 } 364 unzip(archive, out); 365 new File(out, "archetype.xml").delete(); 366 if (elRes != null) { 367 processResources(elRes, out, vars); 368 } 369 } 370 371 public static String readVar(String key, String value) throws IOException { 372 System.out.print(key + (value == null ? ": " : " [" + value + "]: ")); 373 StringBuilder buf = new StringBuilder(); 374 int c = System.in.read(); 375 LOOP: while (c != -1) { 376 if (c == '\n' || c == '\r') { 377 if (buf.length() == 0) { 378 if (value == null) { 379 System.out.println(key + ": "); 380 break LOOP; 381 } else { 382 return value; 383 } 384 } 385 return buf.toString(); 386 } 387 buf.append((char) c); 388 c = System.in.read(); 389 } 390 return value; 391 } 392 393 public static File unzipArchetype(File zipFile) throws IOException { 394 File file = File.createTempFile("nuxeo_archetype_" + zipFile.getName(), ".tmp"); 395 Framework.trackFile(file, file); 396 file.delete(); 397 file.mkdirs(); 398 unzip(zipFile, file); 399 return file; 400 } 401 402 public static void deleteTree(File dir) { 403 emptyDirectory(dir); 404 dir.delete(); 405 } 406 407 public static void emptyDirectory(File dir) { 408 File[] files = dir.listFiles(); 409 if (files == null) { 410 return; 411 } 412 int len = files.length; 413 for (int i = 0; i < len; i++) { 414 File file = files[i]; 415 if (file.isDirectory()) { 416 deleteTree(file); 417 } else { 418 file.delete(); 419 } 420 } 421 } 422 423 public static void writeFile(File file, byte[] buf) throws IOException { 424 FileOutputStream fos = null; 425 try { 426 fos = new FileOutputStream(file); 427 fos.write(buf); 428 } finally { 429 if (fos != null) { 430 fos.close(); 431 } 432 } 433 } 434 435 public static void writeFile(File file, String buf) throws IOException { 436 writeFile(file, buf.getBytes()); 437 } 438 439 public static void launch(String[] args) { 440 // launch an app. 441 } 442 443}