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