1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.startup;
20
21 import java.io.BufferedOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.InputStream;
26 import java.io.IOException;
27 import java.net.JarURLConnection;
28 import java.net.URL;
29 import java.nio.channels.FileChannel;
30 import java.util.Enumeration;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarFile;
33
34 import org.apache.catalina.Host;
35 import org.apache.catalina.util.StringManager;
36 import org.apache.juli.logging.Log;
37 import org.apache.juli.logging.LogFactory;
38
39 /**
40 * Expand out a WAR in a Host's appBase.
41 *
42 * @author Craig R. McClanahan
43 * @author Remy Maucherat
44 * @author Glenn L. Nielsen
45 * @version $Revision: 467222 $
46 */
47
48 public class ExpandWar {
49
50 private static Log log = LogFactory.getLog(ExpandWar.class);
51
52 /**
53 * The string resources for this package.
54 */
55 protected static final StringManager sm =
56 StringManager.getManager(Constants.Package);
57
58
59 /**
60 * Expand the WAR file found at the specified URL into an unpacked
61 * directory structure, and return the absolute pathname to the expanded
62 * directory.
63 *
64 * @param host Host war is being installed for
65 * @param war URL of the web application archive to be expanded
66 * (must start with "jar:")
67 *
68 * @exception IllegalArgumentException if this is not a "jar:" URL
69 * @exception IOException if an input/output error was encountered
70 * during expansion
71 */
72 public static String expand(Host host, URL war)
73 throws IOException {
74
75 // Calculate the directory name of the expanded directory
76 if (host.getLogger().isDebugEnabled()) {
77 host.getLogger().debug("expand(" + war.toString() + ")");
78 }
79 String pathname = war.toString().replace('\\', '/');
80 if (pathname.endsWith("!/")) {
81 pathname = pathname.substring(0, pathname.length() - 2);
82 }
83 int period = pathname.lastIndexOf('.');
84 if (period >= pathname.length() - 4)
85 pathname = pathname.substring(0, period);
86 int slash = pathname.lastIndexOf('/');
87 if (slash >= 0) {
88 pathname = pathname.substring(slash + 1);
89 }
90 if (host.getLogger().isDebugEnabled()) {
91 host.getLogger().debug(" Proposed directory name: " + pathname);
92 }
93 return expand(host, war, pathname);
94
95 }
96
97
98 /**
99 * Expand the WAR file found at the specified URL into an unpacked
100 * directory structure, and return the absolute pathname to the expanded
101 * directory.
102 *
103 * @param host Host war is being installed for
104 * @param war URL of the web application archive to be expanded
105 * (must start with "jar:")
106 * @param pathname Context path name for web application
107 *
108 * @exception IllegalArgumentException if this is not a "jar:" URL
109 * @exception IOException if an input/output error was encountered
110 * during expansion
111 */
112 public static String expand(Host host, URL war, String pathname)
113 throws IOException {
114
115 // Make sure that there is no such directory already existing
116 File appBase = new File(host.getAppBase());
117 if (!appBase.isAbsolute()) {
118 appBase = new File(System.getProperty("catalina.base"),
119 host.getAppBase());
120 }
121 if (!appBase.exists() || !appBase.isDirectory()) {
122 throw new IOException
123 (sm.getString("hostConfig.appBase",
124 appBase.getAbsolutePath()));
125 }
126 File docBase = new File(appBase, pathname);
127 if (docBase.exists()) {
128 // War file is already installed
129 return (docBase.getAbsolutePath());
130 }
131
132 // Create the new document base directory
133 docBase.mkdir();
134
135 // Expand the WAR into the new document base directory
136 JarURLConnection juc = (JarURLConnection) war.openConnection();
137 juc.setUseCaches(false);
138 JarFile jarFile = null;
139 InputStream input = null;
140 try {
141 jarFile = juc.getJarFile();
142 Enumeration jarEntries = jarFile.entries();
143 while (jarEntries.hasMoreElements()) {
144 JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
145 String name = jarEntry.getName();
146 int last = name.lastIndexOf('/');
147 if (last >= 0) {
148 File parent = new File(docBase,
149 name.substring(0, last));
150 parent.mkdirs();
151 }
152 if (name.endsWith("/")) {
153 continue;
154 }
155 input = jarFile.getInputStream(jarEntry);
156
157 // Bugzilla 33636
158 File expandedFile = expand(input, docBase, name);
159 long lastModified = jarEntry.getTime();
160 if ((lastModified != -1) && (lastModified != 0) && (expandedFile != null)) {
161 expandedFile.setLastModified(lastModified);
162 }
163
164 input.close();
165 input = null;
166 }
167 } catch (IOException e) {
168 // If something went wrong, delete expanded dir to keep things
169 // clean
170 deleteDir(docBase);
171 throw e;
172 } finally {
173 if (input != null) {
174 try {
175 input.close();
176 } catch (Throwable t) {
177 ;
178 }
179 input = null;
180 }
181 if (jarFile != null) {
182 try {
183 jarFile.close();
184 } catch (Throwable t) {
185 ;
186 }
187 jarFile = null;
188 }
189 }
190
191 // Return the absolute path to our new document base directory
192 return (docBase.getAbsolutePath());
193
194 }
195
196
197 /**
198 * Copy the specified file or directory to the destination.
199 *
200 * @param src File object representing the source
201 * @param dest File object representing the destination
202 */
203 public static boolean copy(File src, File dest) {
204
205 boolean result = true;
206
207 String files[] = null;
208 if (src.isDirectory()) {
209 files = src.list();
210 result = dest.mkdir();
211 } else {
212 files = new String[1];
213 files[0] = "";
214 }
215 if (files == null) {
216 files = new String[0];
217 }
218 for (int i = 0; (i < files.length) && result; i++) {
219 File fileSrc = new File(src, files[i]);
220 File fileDest = new File(dest, files[i]);
221 if (fileSrc.isDirectory()) {
222 result = copy(fileSrc, fileDest);
223 } else {
224 FileChannel ic = null;
225 FileChannel oc = null;
226 try {
227 ic = (new FileInputStream(fileSrc)).getChannel();
228 oc = (new FileOutputStream(fileDest)).getChannel();
229 ic.transferTo(0, ic.size(), oc);
230 } catch (IOException e) {
231 log.error(sm.getString
232 ("expandWar.copy", fileSrc, fileDest), e);
233 result = false;
234 } finally {
235 if (ic != null) {
236 try {
237 ic.close();
238 } catch (IOException e) {
239 }
240 }
241 if (oc != null) {
242 try {
243 oc.close();
244 } catch (IOException e) {
245 }
246 }
247 }
248 }
249 }
250 return result;
251
252 }
253
254
255 /**
256 * Delete the specified directory, including all of its contents and
257 * subdirectories recursively.
258 *
259 * @param dir File object representing the directory to be deleted
260 */
261 public static boolean delete(File dir) {
262 if (dir.isDirectory()) {
263 return deleteDir(dir);
264 } else {
265 return dir.delete();
266 }
267 }
268
269
270 /**
271 * Delete the specified directory, including all of its contents and
272 * subdirectories recursively.
273 *
274 * @param dir File object representing the directory to be deleted
275 */
276 public static boolean deleteDir(File dir) {
277
278 String files[] = dir.list();
279 if (files == null) {
280 files = new String[0];
281 }
282 for (int i = 0; i < files.length; i++) {
283 File file = new File(dir, files[i]);
284 if (file.isDirectory()) {
285 deleteDir(file);
286 } else {
287 file.delete();
288 }
289 }
290 return dir.delete();
291
292 }
293
294
295 /**
296 * Expand the specified input stream into the specified directory, creating
297 * a file named from the specified relative path.
298 *
299 * @param input InputStream to be copied
300 * @param docBase Document base directory into which we are expanding
301 * @param name Relative pathname of the file to be created
302 * @return A handle to the expanded File
303 *
304 * @exception IOException if an input/output error occurs
305 */
306 protected static File expand(InputStream input, File docBase, String name)
307 throws IOException {
308
309 File file = new File(docBase, name);
310 BufferedOutputStream output = null;
311 try {
312 output =
313 new BufferedOutputStream(new FileOutputStream(file));
314 byte buffer[] = new byte[2048];
315 while (true) {
316 int n = input.read(buffer);
317 if (n <= 0)
318 break;
319 output.write(buffer, 0, n);
320 }
321 } finally {
322 if (output != null) {
323 try {
324 output.close();
325 } catch (IOException e) {
326 // Ignore
327 }
328 }
329 }
330
331 return file;
332 }
333
334
335 }