1 /*
2 * Copyright 2011 SOFTEC sa. All rights reserved.
3 *
4 * This source code is licensed under the Creative Commons
5 * Attribution-NonCommercial-NoDerivs 3.0 Luxembourg
6 * License.
7 *
8 * To view a copy of this license, visit
9 * http://creativecommons.org/licenses/by-nc-nd/3.0/lu/
10 * or send a letter to Creative Commons, 171 Second Street,
11 * Suite 300, San Francisco, California, 94105, USA.
12 */
13
14 package org.codehaus.mojo.javascript;
15
16 import org.apache.maven.artifact.Artifact;
17 import org.apache.maven.artifact.factory.ArtifactFactory;
18 import org.apache.maven.artifact.repository.ArtifactRepository;
19 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
20 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
21 import org.apache.maven.artifact.resolver.ArtifactResolver;
22 import org.apache.maven.plugin.AbstractMojo;
23 import org.apache.maven.plugin.MojoExecutionException;
24 import org.apache.maven.project.MavenProject;
25 import org.codehaus.mojo.javascript.titanium.TitaniumUtils;
26 import org.codehaus.plexus.util.IOUtil;
27
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.text.MessageFormat;
33 import java.util.List;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipInputStream;
36
37 /**
38 * Titanium package abstract base class;
39 */
40 public abstract class AbstractTitaniumPackageMojo extends AbstractMojo {
41 /**
42 * <p>Parameter designed to pick <code>-DandroidBuilder</code>
43 * in case there is no pom with an <code><titaniumSettings></code> configuration tag.</p>
44 * <p>Coresponds to {@link TitaniumSettings#androidBuilder}</p>
45 * @parameter expression="${androidBuilder}"
46 * @readonly
47 */
48 private File androidBuilderPath;
49
50 /**
51 * <p>Parameter designed to pick <code>-DiosBuilder</code>
52 * in case there is no pom with an <code><titaniumSettings></code> configuration tag.</p>
53 * <p>Corresponds to {@link TitaniumSettings#iosBuilder}</p>
54 * @parameter expression="${iosBuilder}"
55 * @readonly
56 */
57 private File iosBuilderPath;
58
59 /**
60 * <p>Parameter designed to pick <code>-DandroidSDK</code>
61 * in case there is no pom with <code><titaniumSettings></code> configuration tag.</p>
62 * <p>Corresponds to {@link TitaniumSettings#androidSDK}</p>
63 * @parameter expression="${androidSDK}"
64 * @readonly
65 */
66 private File androidSDKPath;
67
68 /**
69 * <p>Parameter designed to pick <code>-Dsign.keystore</code>
70 * in case there is no pom with <code><titaniumSettings></code> configuration tag.</p>
71 * <p>Corresponds to {@link TitaniumSettings#keystore}</p>
72 * @parameter expression="${sign.keystore}"
73 * @readonly
74 */
75
76 private String titaniumSettingsKeystore;
77
78 /**
79 * <p>Parameter designed to pick <code>-Dsign.password</code>
80 * in case there is no pom with <code><titaniumSettings></code> configuration tag.</p>
81 * <p>Corresponds to {@link TitaniumSettings#keystorePassword}</p>
82 * @parameter expression="${sign.password}"
83 * @readonly
84 */
85 private String titaniumSettingsKeystorePassword;
86
87 /**
88 * <p>Parameter designed to pick <code>-Dsign.alias</code>
89 * in case there is no pom with <code><titaniumSettings></code> configuration tag.</p>
90 * <p>Corresponds to {@link TitaniumSettings#keystoreAlias}</p>
91 * @parameter expression="${sign.alias}"
92 * @readonly
93 */
94 private String titaniumSettingsKeystoreAlias;
95
96 /**
97 * <p>Parameter designed to pick <code>-DvirtualDevice.androidAPI</code>
98 * in case there is no pom with <code><virtualDevice></code> configuration tag.</p>
99 * <p>Corresponds to {@link VirtualDevice#androidAPI}</p>
100 * @parameter expression="${virtualDevice.androidAPI}"
101 * @readonly
102 */
103 private String virtualDeviceAndroidAPI;
104
105 /**
106 * <p>Parameter designed to pick <code>-DvirtualDevice.iosVersion</code>
107 * in case there is no pom with <code><virtualDevice></code> configuration tag.</p>
108 * <p>Corresponds to {@link VirtualDevice#iosVersion}</p>
109 * @parameter expression="${virtualDevice.iosVersion}"
110 * @readonly
111 */
112 private String virtualDeviceIosVersion;
113
114 /**
115 * <p>Parameter designed to pick <code>-DvirtualDevice.skin</code>
116 * in case there is no pom with <code><virtualDevice></code> configuration tag.</p>
117 * <p>Corresponds to {@link VirtualDevice#skin}</p>
118 * @parameter expression="${virtualDevice.skin}"
119 * @readonly
120 */
121 private String virtualDeviceSkin;
122
123 /**
124 * <p>Parameter designed to pick <code>-DvirualDevice.family</code>
125 * in case there is no pom with <code><virtualDevice></code> configuration tag.</p>
126 * <p>Corresponds to {@link VirtualDevice#family}</p>
127 * @parameter expression="${virtualDevice.family}"
128 * @readonly
129 */
130 private String virtualDeviceFamily;
131
132 /**
133 * <p>Parameter designed to pick <code>-DvirtualDevice.wait</code>
134 * in case there is no pom with <code><virtualDevice></code> configuration tag.</p>
135 * <p>Corresponds to {@link VirtualDevice#wait}</p>
136 * @parameter expression="${virtualDevice.wait}"
137 * @readonly
138 */
139 private Long virtualDeviceWait;
140
141 /**
142 * <p>The platform for which the code should be packaged.</p>
143 * <p>Supported platforms are:</p>
144 * <dl>
145 * <dt>android</dt>
146 * <dd>Package for the android platform.</dd>
147 * <dt>iphone</dt>
148 * <dd>Package for the iPhone platform.</dd>
149 * <dt>ipad</dt>
150 * <dd>Package for the iPad platform.</dd>
151 * <dt>universal</dt>
152 * <dd>Package for iPhone and iPad.</dd>
153 * </dl>
154 *
155 * @parameter expression="${platform}"
156 * @required
157 */
158 protected String platform;
159
160 /**
161 * <p>The version of the platform for which the code should be compiled.</p>
162 * <p>This is the version of the library to use to compile the application.
163 * It's possible to specify another android API for the android virtual device.
164 * See {@link VirtualDevice#androidAPI}.</p>
165 *
166 * @parameter expression="${androidAPI}"
167 */
168 protected String androidAPI;
169
170 /**
171 * The version of the platform for which the code should be compiled.
172 *
173 * @parameter expression="${iosVersion}"
174 */
175 protected String iosVersion;
176
177 /**
178 * The titanium SDK version to use.
179 *
180 * @parameter expression="${titaniumVersion}"
181 * @required
182 */
183 protected String titaniumVersion;
184
185 /**
186 * <p>The titanium settings.</p>
187 * <p>Contains various information needed to execute a titanium build.</p>
188 * <p>Here's the list of the titaniumSettings parameters:</p>
189 * <dl>
190 * <dt>{@link TitaniumSettings#androidBuilder}</dt>
191 * <dd>The titanium android builder.py file location.
192 * Optional.
193 * If not specified it tries to retrieve the builder based on {@link #titaniumVersion}</dd>
194 * <dt>{@link TitaniumSettings#iosBuilder}</dt>
195 * <dd>The titanium iOS builder.py location.
196 * Optional.
197 * If not specified, it tries to retrieve the builder based on {@link #titaniumVersion}</dd>
198 * <dt>{@link TitaniumSettings#androidSDK}</dt>
199 * <dd>The android SDK location.
200 * This parameter is optional, by default the android SDK is retrieved based on the environment
201 * variable ANDROID_HOME.</dd>
202 * <dt>{@link TitaniumSettings#keystore}</dt>
203 * <dd>The android keystore to use to sign the application.
204 * Optional. If not specified the Titanium keystore is used.</dd>
205 * <dt>{@link TitaniumSettings#keystorePassword}</dt>
206 * <dd>The android keystore password.
207 * Optional. If not specified the default titanium keystore password is used.</dd>
208 * <dt>{@link TitaniumSettings#keystoreAlias}</dt>
209 * <dd>The android keystore key alias.
210 * Optional. The alias of the key to use to sign the application.
211 * If not specified the default titanium alias is used.</dd>
212 * <dt>{@link TitaniumSettings#iosDevelopmentProvisioningProfile}</dt>
213 * <dd>The iOS development provisioning profile.
214 * This profile is use when {@link #executeMode} is <code>virtual</code>
215 * or <code>device</code>.</dd>
216 * <dt>{@link TitaniumSettings#iosDistributionProvisioningProfile}</dt>
217 * <dd>The iOS distribution provisioning profile.
218 * This profile is used when {@link #executeMode} is <code>none</code>.</dd>
219 * <dt>{@link TitaniumSettings#iosDevelopmentCertificate}</dt>
220 * <dd>The iOS development certificate.
221 * This certificate is used when {@link #executeMode} is <code>virtual</code>
222 * or <code>device</code>.</dd>
223 * <dt>{@link TitaniumSettings#iosDistributionCertificate}</dt>
224 * <dd>The iOS distribution certificate.
225 * This certificate is used when {@link #executeMode} is <code>none</code>.</dd>
226 * </dl>
227 * @parameter
228 */
229 protected TitaniumSettings titaniumSettings;
230
231 /**
232 * <p>Virtual device configuration.</p>
233 * <p>When {@link #executeMode} is virtual,
234 * the parameters in virtualDevice are used to configure the
235 * android emulator or iphone simulator.</p>
236 * <p>VirtualDevice has the following parameters:</p>
237 * <dl>
238 * <dt>{@link VirtualDevice#androidAPI}</dt>
239 * <dd>The version on which the virtual device should run.
240 * If not specified, the latest android API version will be used.
241 * Regardless of the global androidAPI value.</dd>
242 * <dt>{@link VirtualDevice#iosVersion}</dt>
243 * <dd>The ios version of the virtual device.
244 * If not specified the latest available version will be used.
245 * Regardless of the global parameter value.</dd>
246 * <dt>{@link VirtualDevice#skin}</dt>
247 * <dd>The skin of the android emulator.
248 * Defaults to HVGA for version less than 10 and to WXGA for version greater than 10</dd>
249 * <dt>{@link VirtualDevice#family}</dt>
250 * <dd>The iOS device family.
251 * Valid values are <code>iphone</code> or <code>ipad</code>.</dd>
252 * <dt>{@link VirtualDevice#wait}</dt>
253 * <dd>How much miliseconds to wait after launching emulator before
254 * installing the android application.</dd>
255 * </dl>
256 * @see VirtualDevice
257 * @parameter
258 */
259 protected VirtualDevice virtualDevice;
260
261 /**
262 * <p>The package execution mode.</p>
263 * <p>Allow the execution of the package on an emulator/device.</p>
264 * <p>Values are:</p>
265 * <dl>
266 * <dt>none</dt>
267 * <dd>Do not execute. (Default value)</dd>
268 * <dt>virtual</dt>
269 * <dd>Execute on an emulator whose settings are specified in {@link #virtualDevice}.</dd>
270 * <dt>device</dt>
271 * <dd>Execute on a connected device.</dd>
272 * </dl>
273 *
274 * @parameter default-value="none" expression="${executeMode}"
275 */
276 protected String executeMode;
277
278 /**
279 * The output directory of the packaged titanium files.
280 *
281 * @parameter default-value="${project.build.outputDirectory}"
282 */
283 protected File outputDirectory;
284
285 /**
286 * The ios Development certificate.
287 * @parameter expression="${iosDevelopmentCertificate}"
288 * @readonly
289 */
290 protected String iosDevelopmentCertificate;
291
292 /**
293 * The ios Distribution certificate.
294 * @parameter expression="${iosDistributionCertificate}"
295 * @readonly
296 */
297 protected String iosDistributionCertificate;
298
299
300 /**
301 * The maven project.
302 *
303 * @parameter expression="${project}"
304 * @required
305 * @readonly
306 */
307 protected MavenProject project;
308
309 /**
310 * @component
311 */
312 protected ArtifactFactory artifactFactory;
313
314 /**
315 * @component
316 */
317 protected ArtifactResolver artifactResolver;
318
319 /**
320 * @parameter default-value="${localRepository}"
321 * @readonly
322 */
323 protected ArtifactRepository localRepository;
324
325 /**
326 * @parameter default-value="${project.remoteArtifactRepositories}"
327 * @readonly
328 */
329 protected List remoteRepositories;
330
331 /**
332 * The iOS development provisioning profile.
333 * @parameter expression="${iosDevelopmentProvisioningProfile}"
334 * @readonly
335 */
336 protected String iosDevelopmentProvisioningProfile;
337
338 /**
339 * The ios distribution provisioning profile.
340 * @parameter expression="${iosDistributionProvisioningProfile}"
341 * @readonly
342 */
343 protected String iosDistributionProvisioningProfile;
344
345
346 /**
347 * <p>Retrieve the ios platform version.</p>
348 * <p>If the ios platform version is not specified, retrieve one
349 * from the XCode installation.</p>
350 * @return The iOS platform version.
351 */
352 protected String getIosVersion() {
353 if (iosVersion == null) {
354 iosVersion = TitaniumUtils.getLatestIosPlatformVersion();
355 }
356 return iosVersion;
357 }
358
359 /**
360 * <p>Retrieve the android API to use when building the application.</p>
361 * <p>If the android API is not specified, the latest one is retrieved based on the
362 * Android SDK location.</p>
363 * @return The android API level.
364 * @throws org.apache.maven.plugin.MojoExecutionException
365 */
366 protected String getAndroidAPI() throws MojoExecutionException {
367 if (androidAPI == null) {
368 androidAPI = TitaniumUtils.getLatestAndroidPlatformVersion(getTitaniumSettings().getAndroidSdk());
369 }
370 return androidAPI;
371 }
372
373 /**
374 * Retrieve the titanium project folder for the specified platform.
375 * @return A File representing the titanium project folder.
376 */
377 protected File getTiProjectDirectory() {
378 return new File(outputDirectory, platform);
379 }
380
381 /**
382 * <p>Retrieve the TitaniumSettings object.</p>
383 * <p>If {@link #titaniumSettings} is null, a new TitaniumSettings
384 * is constructed based on the specified expressions.</p>
385 * @return An initialized TitaniumSettings object.
386 * @see #androidSDKPath
387 * @see #iosBuilderPath
388 * @see #androidBuilderPath
389 * @see #titaniumSettingsKeystore
390 * @see #titaniumSettingsKeystorePassword
391 * @see #titaniumSettingsKeystoreAlias
392 */
393 protected TitaniumSettings getTitaniumSettings() {
394 if (titaniumSettings == null) {
395 titaniumSettings = new TitaniumSettings();
396 }
397 if (androidSDKPath != null) {
398 titaniumSettings.setAndroidSdk(androidSDKPath);
399 }
400 if (iosBuilderPath != null) {
401 titaniumSettings.setIosBuilder(iosBuilderPath);
402 }
403 if (androidBuilderPath != null) {
404 titaniumSettings.setAndroidBuilder(androidBuilderPath);
405 }
406 if (titaniumSettingsKeystore != null) {
407 titaniumSettings.setKeystore(titaniumSettingsKeystore);
408 }
409 if (titaniumSettingsKeystoreAlias != null) {
410 titaniumSettings.setKeystoreAlias(titaniumSettingsKeystoreAlias);
411 }
412 if (titaniumSettingsKeystorePassword != null) {
413 titaniumSettings.setKeystorePassword(titaniumSettingsKeystorePassword);
414 }
415 if (iosDevelopmentCertificate != null) {
416 titaniumSettings.setIosDevelopmentCertificate(iosDevelopmentCertificate);
417 }
418 if (iosDistributionCertificate != null) {
419 titaniumSettings.setIosDistributionCertificate(iosDistributionCertificate);
420 }
421 if (iosDevelopmentProvisioningProfile != null) {
422 titaniumSettings.setIosDevelopmentProvisioningProfile(iosDevelopmentProvisioningProfile);
423 }
424 if (iosDistributionProvisioningProfile != null) {
425 titaniumSettings.setIosDistributionProvisioningProfile(iosDistributionProvisioningProfile);
426 }
427 return titaniumSettings;
428 }
429
430 /**
431 * <p>Retrieve the VirtualDevice object.</p>
432 * <p>If {@link #virtualDevice} is null, a new VirtualDevice
433 * is constructed based on the specified expressions.</p>
434 * @return An initialized VirtualDevice object.
435 * @see #virtualDeviceAndroidAPI
436 * @see #virtualDeviceFamily
437 * @see #virtualDeviceIosVersion
438 * @see #virtualDeviceSkin
439 * @see #virtualDeviceWait
440 */
441 protected VirtualDevice getVirtualDevice() {
442 if (virtualDevice == null) {
443 virtualDevice = new VirtualDevice();
444 }
445 if (this.virtualDeviceAndroidAPI != null) {
446 virtualDevice.setAndroidAPI(this.virtualDeviceAndroidAPI);
447 }
448 if (this.virtualDeviceFamily != null) {
449 virtualDevice.setFamily(this.virtualDeviceFamily);
450 }
451 if (this.virtualDeviceIosVersion != null) {
452 virtualDevice.setIosVersion(this.virtualDeviceIosVersion);
453 }
454 if (this.virtualDeviceSkin != null) {
455 virtualDevice.setSkin(this.virtualDeviceSkin);
456 }
457 if (this.virtualDeviceWait != null) {
458 virtualDevice.setWait(this.virtualDeviceWait);
459 }
460 return virtualDevice;
461 }
462
463 /**
464 * <p>Check the titanium pom settings.</p>
465 * <p>Mainly check if the {@link #androidAPI} or the {@link #iosVersion} parameters are valid.</p>
466 * @return true if the pom settings are not valid.
467 * @throws MojoExecutionException If an error occurs.
468 */
469 protected boolean checkPomSettings() throws MojoExecutionException {
470 if (platform.equals("android")) {
471 if (!TitaniumUtils.isAndroidVersionValid(getTitaniumSettings().getAndroidSdk(), getAndroidAPI())) {
472 try {
473 List<Integer> availApis = TitaniumUtils.getAvailableAndroidPlatformVersions(getTitaniumSettings().getAndroidSdk());
474 StringBuilder availPlatforms = new StringBuilder();
475 for (int i=0; i<availApis.size(); i++) {
476 if (i>0) {
477 availPlatforms.append(", ");
478 }
479 availPlatforms.append(availApis.get(i).toString());
480 }
481 String errorMsg = MessageFormat.format("The ios version {0} is not valid. Valid versions are ${1}.",
482 getAndroidAPI(), availPlatforms.toString());
483 getLog().error(errorMsg);
484 } catch (Throwable t) {
485 getLog().error("The specified android API is not valid");
486 }
487 return false;
488 }
489 } else if (platform.equals("iphone") || platform.equals("ipad") || platform.equals("universal")) {
490 if (!TitaniumUtils.isIphoneVersionValid(getIosVersion())) {
491 try {
492 List<String> platforms = TitaniumUtils.listAvailableIosPlatformVersions();
493 StringBuilder availPlatforms = new StringBuilder();
494 if (platforms != null) {
495 for(int i=0; i<platforms.size(); i++) {
496 if (i>0) {
497 availPlatforms.append(", ");
498 }
499 availPlatforms.append(platforms.get(i));
500 }
501 }
502 String errorMsg = MessageFormat.format("The ios version {0} is not valid. Valid versions are ${1}.",
503 getIosVersion(), availPlatforms.toString());
504 getLog().error(errorMsg);
505 } catch (Throwable t) {}
506 return false;
507 }
508 }
509
510 return true;
511 }
512
513 protected void downloadTitaniumFromRepositories(String tiVersion, File targetDir) throws ArtifactResolutionException, ArtifactNotFoundException, IOException {
514 final String tiGroupId = "org.appcelerator.titanium";
515 final String tiArtifactId = "titanium-mobile";
516
517 String classifier = TitaniumUtils.getOsClassifier();
518 Artifact artifact = artifactFactory.createArtifactWithClassifier(tiGroupId, tiArtifactId, tiVersion, "zip", classifier);
519
520 artifactResolver.resolve(artifact, remoteRepositories, localRepository);
521
522 File file = artifact.getFile();
523
524 extractZipFile(file, targetDir);
525 }
526
527 protected File resolveAndroidBuilder() throws MojoExecutionException {
528 File destTiFolder = new File(outputDirectory, "titanium_mobile");
529
530 File builderFile = getTitaniumSettings().getAndroidBuilder(titaniumVersion);
531 if (builderFile == null) {
532 try {
533 builderFile = new File(destTiFolder, "mobilesdk" + File.separator
534 + TitaniumUtils.getOsClassifier() + File.separatorChar
535 + titaniumVersion + File.separatorChar
536 + "android" + File.separatorChar + "builder.py");
537
538 if (!builderFile.exists()) {
539 getLog().info("Downloading Titanium SDK from repository...");
540 downloadTitaniumFromRepositories(titaniumVersion, destTiFolder);
541 }
542 if ( !builderFile.exists()) {
543 getLog().error("Unable to retrieve android builder from extracted artifact");
544 builderFile = null;
545 throw new MojoExecutionException("Unable to retrieve android builder from extracted artifact");
546 } else {
547 getLog().info("Using artifact android builder: " + builderFile.getAbsolutePath());
548 }
549 } catch (ArtifactResolutionException e) {
550 throw new MojoExecutionException("Unable to resolve Titanium SDK artifact", e);
551 } catch (ArtifactNotFoundException e) {
552 throw new MojoExecutionException("Unable to find the titanium SDK artifact to download for version " + titaniumVersion, e);
553 } catch (IOException e) {
554 throw new MojoExecutionException("An error occured while extracting the titanium mobile SDK", e);
555 }
556 }
557
558 return builderFile;
559 }
560
561 protected File resolveIOSBuilder() throws MojoExecutionException {
562 File destTiFolder = new File(outputDirectory, "titanium_mobile");
563
564 File builderFile = getTitaniumSettings().getIosBuilder(titaniumVersion);
565 if (builderFile == null) {
566 builderFile = new File(destTiFolder, "mobilesdk" + File.separator
567 + TitaniumUtils.getOsClassifier() + File.separator
568 + titaniumVersion + File.separator
569 + "iphone" + File.separator + "builder.py");
570
571 if (!builderFile.exists()) {
572 getLog().info("Downloading Titanium SDK from repository...");
573 try {
574 downloadTitaniumFromRepositories(titaniumVersion, destTiFolder);
575 } catch (ArtifactResolutionException e) {
576 throw new MojoExecutionException("Unable to resolve Titanium SDK artifact", e);
577 } catch (ArtifactNotFoundException e) {
578 throw new MojoExecutionException("Unable to find the titanium SDK artifact to download for version " + titaniumVersion, e);
579 } catch (IOException e) {
580 throw new MojoExecutionException("An error occured while extracting the titanium mobile SDK", e);
581 }
582 }
583 if ( !builderFile.exists()) {
584 getLog().error("Unable to retrieve ios builder from extracted artifact");
585 builderFile = null;
586 throw new MojoExecutionException("Unable to retrieve ios builder from extracted artifact");
587 } else {
588 getLog().info("Using artifact ios builder: " + builderFile.getAbsolutePath());
589 }
590
591 }
592
593 return builderFile;
594 }
595
596 protected void extractZipFile(File zipFile, File destFolder) throws IOException {
597 final int BUFFER_SIZE = 1024;
598 byte[] buffer = new byte[BUFFER_SIZE];
599
600 ZipInputStream zis = null;
601 ZipEntry zipEntry;
602
603 try {
604 zis = new ZipInputStream(new FileInputStream(zipFile));
605
606 while ((zipEntry = zis.getNextEntry()) != null) {
607 String entryName = zipEntry.getName();
608 File newFile = new File(destFolder, entryName);
609 if (newFile.getParentFile() != null) {
610 newFile.getParentFile().mkdirs();
611 }
612
613 FileOutputStream fos = null;
614 try {
615 fos = new FileOutputStream(newFile);
616 int n;
617
618 while ((n = zis.read(buffer, 0, BUFFER_SIZE)) > -1) {
619 fos.write(buffer, 0, n);
620 }
621 } finally {
622 IOUtil.close(fos);
623 }
624 zis.closeEntry();
625
626 newFile.setReadable(true);
627 newFile.setWritable(true);
628 if (getFileExtension(newFile).equals("py")) {
629 newFile.setExecutable(true);
630 }
631 }
632 } finally {
633 IOUtil.close(zis);
634 }
635 }
636
637 private String getFileExtension(final File file) {
638 int mid = file.getName().lastIndexOf(".");
639 if (mid != -1) {
640 return file.getName().substring(mid+1, file.getName().length());
641 } else {
642 return "";
643 }
644 }
645 }