View Javadoc

1   /*   Copyright (C) 2004 Finalist IT Group
2    *
3    *   This file is part of JAG - the Java J2EE Application Generator
4    *
5    *   JAG is free software; you can redistribute it and/or modify
6    *   it under the terms of the GNU General Public License as published by
7    *   the Free Software Foundation; either version 2 of the License, or
8    *   (at your option) any later version.
9    *   JAG is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU General Public License for more details.
13   *   You should have received a copy of the GNU General Public License
14   *   along with JAG; if not, write to the Free Software
15   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   */
17  
18  package com.finalist.tools.ant.taskdefs;
19  
20  import org.apache.tools.ant.BuildException;
21  import org.apache.tools.ant.Task;
22  import org.apache.tools.ant.taskdefs.Copy;
23  import org.apache.tools.ant.taskdefs.Cvs;
24  import org.apache.tools.ant.taskdefs.Delete;
25  import org.apache.tools.ant.taskdefs.Get;
26  import org.apache.tools.ant.taskdefs.Mkdir;
27  import org.w3c.dom.Document;
28  import org.w3c.dom.NamedNodeMap;
29  import org.w3c.dom.Node;
30  import org.w3c.dom.NodeList;
31  import org.xml.sax.SAXException;
32  
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.parsers.ParserConfigurationException;
36  import java.io.File;
37  import java.io.FileFilter;
38  import java.io.FileInputStream;
39  import java.io.FileOutputStream;
40  import java.io.IOException;
41  import java.net.MalformedURLException;
42  import java.net.URL;
43  import java.util.ArrayList;
44  import java.util.Arrays;
45  import java.util.HashSet;
46  import java.util.Iterator;
47  import java.util.LinkedList;
48  import java.util.List;
49  import java.util.Properties;
50  
51  
52  /***
53   * This task is used to checkout 3rd-party libraries. The libraries that are to be
54   * checked out are read from the libXmlFile specified in the build.xml file. They
55   * are checked out to the libDir which is also specified in the build.xml file. In
56   * the versions.properties file the tags of the checked out files are stored. If a
57   * stored tag does not comply with the tag specified in the libXmlFile, the file is
58   * deleted and checked out again to get the correct version. Files that are removed
59   * from the libXmlFile but still exist in the libDir will be removed.
60   * <p>
61   * An individual library may be fetched from one of two kinds of source:
62   * <li>the library is checked out from a CVS server, or</li>
63   * <li>the library is fetched from a URL.</li>
64   * The source if configured per library in the accompanying lib.xml file.
65   * If the library is fetched from a URL, no check is done on the server
66   * so it can be used locally.
67   * <p>
68   * Usage in build.xml:
69   * <p><code>
70   * &lt;target description="CVS-checkout for needed JARs." name="checkout.lib" depends="init.ant"&gt;<br>
71   *    &lt;taskdef name="checkout"                                                                   <br>
72   *             classname="com.finalist.tools.ant.taskdefs.CheckoutLibTask"                          <br>
73   *             classpathref="ant.classpath"/&gt;                                                    <br>
74   *    &lt;checkout libXmlFile="lib.xml" libDir="${lib.dir}"/&gt;                                    <br>
75   * &lt;/target&gt;                                                                                  <br>
76   * </code>
77   */
78  public class CheckoutLibTask extends Task {
79  
80     /*** destination directory */
81     private File libDir;
82     /*** Libraries which are read from the libXmlFile */
83     private List libs = new LinkedList();
84     private List localLibs = new LinkedList();
85     private File libXmlFile;
86     private File tmpDir;
87     /*** The name of the properties file created by this task, containing the latest checked-out lib versions. */
88     public final static String LIBVERSION_PROPERTIES_FILENAME = "libversion.properties";
89  
90  
91     /*** Method which is called by ant to execute the task.
92      * @throws BuildException Exception which is thrown to stop the build process and display an error message
93      */
94     public void execute() throws BuildException {
95        Properties props = new Properties();
96        File versionPropFile = null;
97  
98        try {
99           try {
100             readXML();
101 
102          }
103          catch (Exception e) {
104             e.printStackTrace();
105             throw new BuildException("Error reading the library xml!");
106          }
107          if (libDir == null) {
108             libDir = new File("lib"); // default
109          }
110          if (!libDir.exists()) {
111             libDir.mkdirs();
112          }
113          if (!libDir.isDirectory()) {
114             throw new BuildException("Destination directory not properly set.");
115          }
116          emptyTempDir();
117 
118          HashSet okLibs = new HashSet();
119          versionPropFile = new File(libDir, LIBVERSION_PROPERTIES_FILENAME);
120          if (!versionPropFile.exists()) {
121             versionPropFile.createNewFile();
122          }
123          props.load(new FileInputStream(versionPropFile));
124          List existingLibFiles = new ArrayList(Arrays.asList(libDir.listFiles(new FileFilter() {
125             public boolean accept(File file) {
126                return !file.isDirectory();
127             }
128          })));
129 
130 
131          // The following files will never show up in the lib.xml, so ignore them.
132          existingLibFiles.remove(new File(libDir, "lib.dtd"));
133          existingLibFiles.remove(new File(libDir, "lib.xml"));
134          existingLibFiles.remove(new File(libDir, LIBVERSION_PROPERTIES_FILENAME));
135          existingLibFiles.remove(new File(libDir, ".cvsignore"));
136 
137          Iterator libIterator = libs.iterator();
138          while (libIterator.hasNext()) {
139             // if something goes wrong / user interrupts the task half-way through updating a long list of libraries,
140             // we still want to store the versions that were successfully updated up to the point of failure - so..
141             props.store(new FileOutputStream(versionPropFile), "Current versions of 3rd-party libraries");
142 
143             Lib lib = (Lib) libIterator.next();
144             String filename = lib.getFilename();
145             String versionTag = props.getProperty(filename);
146             File existingLibFile = new File(libDir, filename);
147             if (!existingLibFiles.contains(existingLibFile) &&
148                !okLibs.contains(existingLibFile)) {
149                //check out the library for the first time:
150                checkout(lib);
151                okLibs.add(existingLibFile);
152                props.setProperty(filename, lib.getVersion());
153                continue;
154             }
155 
156             //the library already exists..
157             // Only check it out if it is a new version (cvs tag). Not for HTTP or FILE libs.
158             existingLibFiles.remove(existingLibFile);
159             if ( (versionTag == null || !versionTag.equals(lib.getVersion())) && (lib.getVersion().indexOf("://") == -1))   {
160                //..but it's the wrong version, only for CVS - update!
161                existingLibFile.delete();
162                checkout(lib);
163                props.setProperty(filename, lib.getVersion());
164             }
165             else {
166                log(filename + " up to date.");
167                okLibs.add(existingLibFile);
168             }
169          }
170 
171          Iterator localLibIterator = localLibs.iterator();
172          while (localLibIterator.hasNext()) {
173             String filename = (String) localLibIterator.next();
174             log("keeping " + filename + " for testing puposes");
175             log("!!!!! check in in CVS and update lib.xml if you check in any source that uses this library !!!!!");
176             existingLibFiles.remove(new File(libDir, filename));
177             props.remove(filename);
178          }
179 
180          Iterator existingLibFileIterator = existingLibFiles.iterator();
181          while (existingLibFileIterator.hasNext()) {
182             File existingLibFile = (File) existingLibFileIterator.next();
183             String filename = existingLibFile.getName();
184             boolean deleted = existingLibFile.delete();
185             if (!deleted) {
186                throw new BuildException("Deletion of " + filename + " failed!!!!!!!!!!\n" +
187                   filename + " is probably part of a classpath which is used by the checkoutLibTask.\n" +
188                   "Check the buildfile. The classpath should only contain finalist-ant.jar");
189             }
190             else
191                log("Deleted " + existingLibFile.getAbsolutePath());
192             props.remove(filename);
193          }
194 
195          Delete delete = (Delete) project.createTask("delete");
196          delete.setDir(tmpDir);
197          delete.execute();
198 
199       }
200       catch (IOException ie) {
201          ie.printStackTrace();
202          throw new BuildException("Error reading/writing files!");
203       }
204    }
205 
206 
207    /*** Optional destination directory, defaults to "lib".
208     * @param libDir Directory in which the checked out libraries should be stored.
209     */
210    public void setLibDir(File libDir) {
211       // Note: f will automatically be absolute (resolved from project basedir).
212       this.libDir = libDir;
213    }
214 
215 
216    /*** Setter for property libFile.
217     * @param libXmlFile The lib file which specifies what files are to be checked out
218     */
219    public void setLibXmlFile(File libXmlFile) {
220       this.libXmlFile = libXmlFile;
221    }
222 
223 
224    /*** Setter for property tmpDir.
225     * @param tmpDir New value of property tmpDir.
226     *
227     */
228    public void setTmpDir(File tmpDir) {
229       this.tmpDir = tmpDir;
230    }
231 
232 
233    /*** Reads the libraries and their tags from the libXmlFile, stores the data in Lib
234     * classes and adds these classes to the libs List.
235     * @throws ParserConfigurationException Indicates a serious configuration error.
236     * @throws SAXException Encapsulate a general SAX error or warning.
237     * @throws IOException Signals that an I/O exception of some sort has occurred
238     */
239    private void readXML() throws ParserConfigurationException, SAXException, IOException {
240       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
241       dbf.setValidating(true);
242       DocumentBuilder db = dbf.newDocumentBuilder();
243       Document doc = db.parse(libXmlFile);
244       NodeList nl = doc.getElementsByTagName("lib");
245       for (int i = 0; i < nl.getLength(); i++) {
246          Node node = nl.item(i);
247          NamedNodeMap atts = node.getAttributes();
248          String version;
249          String file;
250          Node url = atts.getNamedItem("url");
251          if (url != null) {
252             file = url.getNodeValue();
253             version = file;
254          }
255          else {
256             version = atts.getNamedItem("version").getNodeValue();
257             file = atts.getNamedItem("file").getNodeValue();
258          }
259          Lib lib = new Lib();
260          lib.setVersion(version);
261          lib.setFile(file);
262          libs.add(lib);
263       }
264       nl = doc.getElementsByTagName("localLib");
265       for (int i = 0; i < nl.getLength(); i++) {
266          Node node = nl.item(i);
267          NamedNodeMap atts = node.getAttributes();
268          String file = atts.getNamedItem("file").getNodeValue();
269          localLibs.add(file);
270       }
271 
272    }
273 
274 
275    /*** Checks out a library
276     * @param lib Library to be checked out
277     */
278    private void checkout(Lib lib) {
279       if (lib.getVersion().indexOf("://") != -1) {
280          getFromURL(lib);
281       }
282       else {
283          log("File: " + lib.getFile() + "\n" +
284             "Tag:  " + lib.getVersion() + "\n" +
285             "checking out to " + libDir.getAbsolutePath());
286          Cvs cvs = (Cvs) project.createTask("cvs");
287          cvs.setFailOnError(true);
288          cvs.setCommand("export -r " + lib.getVersion() +
289             " -d " + tmpDir.getName() +
290             " " + lib.getFile());
291          cvs.execute();
292 
293          Copy copy = (Copy) project.createTask("copy");
294          copy.setFile(new File(tmpDir, lib.getFilename()));
295          copy.setTodir(libDir);
296          copy.setOverwrite(true);
297          copy.execute();
298       }
299    }
300 
301 
302    /*** Gets a library via HTTP from a URL.
303     * @param lib Library to be checked out
304     */
305    private void getFromURL(Lib lib) {
306       log("URL: " + lib.getFile() + "\n" +
307          "checking out to " + libDir.getAbsolutePath());
308       Get get = (Get) project.createTask("get");
309       try {
310          URL url = new URL(lib.getFile());
311          get.setSrc(url);
312          get.setDest(new File(libDir, new File(url.getFile()).getName()));
313          get.execute();
314 
315       }
316       catch (MalformedURLException e) {
317          log("ERROR! Can't get library from invalid URL: " + lib.getFile());
318       }
319    }
320 
321 
322    /*** Deletes all files whithin the temporary directory if any exist. This is
323     * necessary to delete the directory, or to checkout files which allready exist
324     * within the temporary directory.
325     */
326    private void emptyTempDir() {
327       Delete delete = (Delete) project.createTask("delete");
328       delete.setDir(tmpDir);
329       delete.execute();
330       Mkdir mkdir = (Mkdir) project.createTask("mkdir");
331       mkdir.setDir(tmpDir);
332       mkdir.execute();
333    }
334 
335 }