View Javadoc

1   /*--------------------------------------------------------------------------
2    *  Copyright 2008 utgenome.org
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *--------------------------------------------------------------------------*/
16  //--------------------------------------
17  // utgb-shell Project
18  //
19  // Maven.java
20  // Since: Jan 11, 2008
21  //
22  // $URL$ 
23  // $Author$
24  //--------------------------------------
25  package org.utgenome.shell;
26  
27  import java.io.BufferedInputStream;
28  import java.io.BufferedReader;
29  import java.io.File;
30  import java.io.FileOutputStream;
31  import java.io.FileReader;
32  import java.io.FileWriter;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.InputStreamReader;
36  import java.net.URL;
37  import java.net.URLConnection;
38  import java.util.Date;
39  import java.util.Map.Entry;
40  import java.util.Properties;
41  import java.util.concurrent.ExecutorService;
42  import java.util.concurrent.Executors;
43  import java.util.concurrent.Future;
44  import java.util.concurrent.TimeUnit;
45  
46  import org.apache.tools.bzip2.CBZip2InputStream;
47  import org.apache.tools.tar.TarEntry;
48  import org.apache.tools.tar.TarInputStream;
49  import org.xerial.core.XerialException;
50  import org.xerial.lens.SilkLens;
51  import org.xerial.silk.SilkWriter;
52  import org.xerial.util.FileResource;
53  import org.xerial.util.StringUtil;
54  import org.xerial.util.log.Logger;
55  
56  /**
57   * Maven utility
58   * 
59   * @author leo
60   * 
61   */
62  public class Maven extends UTGBShellCommand {
63  
64  	private static Logger _logger = Logger.getLogger(Maven.class);
65  
66  	private static String MAVEN_VERSION = "3.0-beta-1";
67  
68  	public static boolean isMavenInstalled() {
69  		String utgbHome = System.getProperty("utgb.home");
70  		if (utgbHome == null)
71  			return false;
72  		else
73  			return new File(utgbHome, "maven").exists();
74  	}
75  
76  	private static class MavenArchiveInfo {
77  		public long extracted = -1;
78  		public String mavenFolder;
79  	}
80  
81  	/**
82  	 * Extract the Maven binary from the archive inside the JAR
83  	 * 
84  	 * @return MAVEN_HOME, where Mavan library is included.
85  	 * @throws IOException
86  	 */
87  	static String extractMaven() throws IOException {
88  
89  		final String utgbFolder = System.getProperty("user.home") + "/.utgb";
90  		final String mavenFolderName = utgbFolder + "/maven";
91  
92  		File mavenFolder = new File(mavenFolderName);
93  		if (!mavenFolder.exists())
94  			mavenFolder.mkdirs();
95  
96  		MavenArchiveInfo archiveInfo = new MavenArchiveInfo();
97  		File mavenArchiveInfoFile = new File(mavenFolderName, "maven-archive.silk");
98  		if (mavenArchiveInfoFile.exists()) {
99  			try {
100 				SilkLens.loadSilk(archiveInfo, new FileReader(mavenArchiveInfoFile));
101 			}
102 			catch (XerialException e) {
103 				_logger.error(e);
104 			}
105 		}
106 
107 		String mavenBinParentFolder = null;
108 
109 		URL mavenArchive = FileResource.find("org.utgenome.shell.archive", "apache-maven-" + MAVEN_VERSION + "-bin.tar.bz2");
110 
111 		URLConnection openConnection = mavenArchive.openConnection();
112 		long archiveDate = openConnection.getLastModified();
113 
114 		if (archiveInfo.extracted > archiveDate) {
115 			if (archiveInfo.mavenFolder != null) {
116 				File mavenFolderWithVersion = new File(mavenFolder, archiveInfo.mavenFolder);
117 				if (mavenFolderWithVersion.exists()) {
118 					if (new File(mavenFolderWithVersion, "bin/mvn").exists()) {
119 						_logger.info("Maven is already installed.");
120 						// already extracted
121 						return mavenFolderWithVersion.getAbsolutePath();
122 					}
123 				}
124 			}
125 		}
126 
127 		_logger.info("Extracting Maven binaries...");
128 
129 		String relativePathOfMavenArchiveFolder = null;
130 		BufferedInputStream bufferedInputStream = new BufferedInputStream(openConnection.getInputStream());
131 		try {
132 			// read two bytes "BZ"
133 			bufferedInputStream.read();
134 			bufferedInputStream.read();
135 			TarInputStream tis = new TarInputStream(new CBZip2InputStream(bufferedInputStream));
136 			TarEntry nextEntry = null;
137 			while ((nextEntry = tis.getNextEntry()) != null) {
138 				int mode = nextEntry.getMode();
139 				String name = nextEntry.getName();
140 				Date modTime = nextEntry.getModTime();
141 
142 				if (name.endsWith("/bin/mvn")) {
143 					relativePathOfMavenArchiveFolder = name.replace("/bin/mvn", "");
144 					mavenBinParentFolder = new File(mavenFolderName, relativePathOfMavenArchiveFolder).getAbsolutePath();
145 				}
146 
147 				File extractedFile = new File(mavenFolder, name);
148 
149 				if (extractedFile.exists() && extractedFile.lastModified() == modTime.getTime())
150 					continue;
151 
152 				if (!nextEntry.isDirectory()) {
153 					_logger.info(String.format("extracted %s into %s", name, mavenFolder.getPath()));
154 
155 					File parent = extractedFile.getParentFile();
156 					if (parent != null && !parent.exists())
157 						parent.mkdirs();
158 
159 					FileOutputStream fo = new FileOutputStream(extractedFile);
160 					try {
161 						tis.copyEntryContents(fo);
162 					}
163 					finally {
164 						fo.close();
165 					}
166 
167 				}
168 				else {
169 					if (!extractedFile.exists())
170 						extractedFile.mkdirs();
171 				}
172 
173 				// chmod
174 				if (!System.getProperty("os.name").contains("Windows")) {
175 					try {
176 						int m = mode & 00777;
177 						String modeStr = Integer.toOctalString(m);
178 						String cmd = String.format("chmod %s %s", modeStr, extractedFile.getAbsolutePath());
179 
180 						CommandExecutor.exec(String.format(cmd));
181 					}
182 					catch (Throwable e) {
183 						_logger.error(e);
184 					}
185 				}
186 
187 				extractedFile.setLastModified(modTime.getTime());
188 
189 			}
190 
191 			if (mavenBinParentFolder == null)
192 				throw new IllegalStateException("maven binary is not found in the archive");
193 		}
194 		finally {
195 			bufferedInputStream.close();
196 		}
197 
198 		// write archive info
199 		archiveInfo.mavenFolder = relativePathOfMavenArchiveFolder;
200 		archiveInfo.extracted = new Date().getTime();
201 
202 		SilkWriter silk = new SilkWriter(new FileWriter(mavenArchiveInfoFile));
203 		silk.preamble();
204 		silk.node("archive").toSilk(archiveInfo);
205 		silk.endDocument();
206 		silk.close();
207 
208 		return mavenBinParentFolder;
209 	}
210 
211 	static String getMavenBinary() {
212 
213 		String utgbHome = System.getProperty("utgb.home");
214 		String osName = System.getProperty("os.name");
215 		if (osName == null)
216 			throw new IllegalStateException("cannot find out your OS name or os.name JVM property is wrongly set somewhere");
217 
218 		String mavenStartupScript = (osName.contains("Windows")) ? "mvn.bat" : "mvn";
219 
220 		if (utgbHome == null) {
221 			try {
222 				String mavenHome = extractMaven();
223 				return new File(mavenHome, "bin/" + mavenStartupScript).getPath();
224 			}
225 			catch (IOException e) {
226 				throw new IllegalStateException("cannot extract Maven binaries: " + e.getMessage());
227 			}
228 		}
229 		else
230 			return new File(utgbHome, "maven/bin/" + mavenStartupScript).getPath();
231 	}
232 
233 	public static void runMaven(String arg) throws UTGBShellException {
234 		runMaven(arg.split("[\\s]+"));
235 	}
236 
237 	public static void runMaven(String arg, File workingDir) throws UTGBShellException {
238 		runMaven(arg.split("[\\s]+"), workingDir);
239 		// runEmbeddedMaven(arg.split("[\\s]+"), workingDir);
240 	}
241 
242 	public static void runMaven(String[] args) throws UTGBShellException {
243 		runMaven(args, null);
244 		// runEmbeddedMaven(args, null);
245 	}
246 
247 	private static abstract class ProcessOutputReader implements Runnable {
248 		private final BufferedReader reader;
249 
250 		public ProcessOutputReader(InputStream in) {
251 			reader = new BufferedReader(new InputStreamReader(in));
252 		}
253 
254 		public abstract void output(String line);
255 
256 		public void run() {
257 			try {
258 				String line;
259 				while ((line = reader.readLine()) != null) {
260 					output(line);
261 				}
262 			}
263 			catch (IOException e) {
264 				// If the process is already terminated, IOException (bad file descriptor) might be reported.
265 				_logger.debug(e);
266 			}
267 		}
268 	}
269 
270 	public static class CommandExecutor {
271 		final ExecutorService threadManager = Executors.newFixedThreadPool(2);
272 		Process proc = null;
273 		Future<?> stdoutReader;
274 		Future<?> stderrReader;
275 
276 		private void dispose() {
277 			if (proc != null) {
278 				proc.destroy();
279 				proc = null;
280 			}
281 
282 			threadManager.shutdown();
283 			try {
284 				while (!threadManager.awaitTermination(1L, TimeUnit.SECONDS)) {
285 				}
286 			}
287 			catch (InterruptedException e) {
288 				_logger.error(e);
289 			}
290 		}
291 
292 		public int execCommand(String commandLine, String[] envp, File workingDir) throws IOException {
293 			try {
294 				if (_logger.isDebugEnabled())
295 					_logger.debug(commandLine);
296 
297 				proc = Runtime.getRuntime().exec(commandLine, envp, workingDir);
298 
299 				// pipe the program's stdout and stderr to the logger
300 				stdoutReader = threadManager.submit(new ProcessOutputReader(proc.getInputStream()) {
301 					@Override
302 					public void output(String line) {
303 						_logger.info(line);
304 					}
305 				});
306 				stderrReader = threadManager.submit(new ProcessOutputReader(proc.getErrorStream()) {
307 					@Override
308 					public void output(String line) {
309 						_logger.error(line);
310 					}
311 				});
312 
313 				int ret = proc.waitFor();
314 				return ret;
315 			}
316 			catch (InterruptedException e) {
317 				_logger.error(e);
318 				return 0;
319 			}
320 			finally {
321 				dispose();
322 			}
323 
324 		}
325 
326 		public static int exec(String commandLine) throws IOException {
327 			return exec(commandLine, null, null);
328 		}
329 
330 		public static int exec(String commandLine, String[] envp, File workingDir) throws IOException {
331 			CommandExecutor e = new CommandExecutor();
332 			return e.execCommand(commandLine, envp, workingDir);
333 		}
334 
335 	}
336 
337 	public static String[] prepareEnvironmentVariables(File mavenHome) {
338 		Properties env = new Properties();
339 		for (Entry<String, String> eachEnv : System.getenv().entrySet()) {
340 			env.setProperty(eachEnv.getKey(), eachEnv.getValue());
341 		}
342 		if (!env.contains("JAVA_HOME") || env.getProperty("JAVA_HOME").contains("jre")) {
343 			env.setProperty("JAVA_HOME", System.getProperty("java.home"));
344 		}
345 		if (mavenHome != null && !env.contains("M2_HOME")) {
346 			env.setProperty("M2_HOME", mavenHome.getAbsolutePath());
347 		}
348 
349 		String[] envp = new String[env.size()];
350 		int index = 0;
351 		for (Object each : env.keySet()) {
352 			String key = each.toString();
353 			envp[index++] = String.format("%s=%s", key, env.getProperty(key));
354 		}
355 
356 		_logger.trace("environment variables: " + env);
357 		return envp;
358 	}
359 
360 	public static int runMaven(String[] args, File workingDir) throws UTGBShellException {
361 
362 		try {
363 
364 			// add the hook for killing the Maven process when ctrl+C is pressed
365 			Runtime.getRuntime().addShutdownHook(new Thread() {
366 				@Override
367 				public void run() {
368 					_logger.debug("shutdown hook is called");
369 				}
370 			});
371 
372 			String mavenBinary = getMavenBinary();
373 
374 			_logger.debug("Maven binary: " + mavenBinary);
375 			File mavenHome = new File(mavenBinary).getParentFile().getParentFile();
376 
377 			String[] envp = prepareEnvironmentVariables(mavenHome);
378 
379 			String cmdLineFormat = System.getProperty("os.name").contains("Windows") ? "\"%s\" %s" : "%s %s";
380 			int returnCode = CommandExecutor.exec(String.format(cmdLineFormat, mavenBinary, StringUtil.join(args, " ")), envp, workingDir);
381 
382 			if (returnCode != 0)
383 				throw new UTGBShellException("error: " + returnCode);
384 
385 			return returnCode;
386 		}
387 		catch (Exception e) {
388 			throw new UTGBShellException(e);
389 		}
390 	}
391 
392 	@Override
393 	public void execute(String[] args) throws Exception {
394 		runMaven(args);
395 	}
396 
397 	@Override
398 	public String name() {
399 		return "maven";
400 	}
401 
402 	@Override
403 	public String getOneLinerDescription() {
404 		return "execute maven tasks";
405 	}
406 
407 }