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}