From c95733a781d36d4db90265b4050d7847cc6e1337 Mon Sep 17 00:00:00 2001 From: lasanthaDLPDS Date: Tue, 20 Mar 2018 17:14:15 +0530 Subject: [PATCH] Add iOS applications and Web Clip publishing functionality This commit contains followings Added dependencies for URL validation and plist parsing. Added version retrieval functionality for iOS application Added WEB clip publishing functionality. In order to compatible with above functionalities modified the source --- .../application/mgt/common/Application.java | 10 - .../mgt/common/ApplicationRelease.java | 10 + .../mgt/common/ApplicationType.java | 2 +- .../pom.xml | 10 + .../impl/ApplicationStorageManagerImpl.java | 217 ++++++++++++++++-- .../application/mgt/core/util/Constants.java | 8 +- .../services/ApplicationManagementAPI.java | 2 +- .../impl/ApplicationManagementAPIImpl.java | 10 +- 8 files changed, 226 insertions(+), 43 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/Application.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/Application.java index 590633c250..646d03affd 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/Application.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/Application.java @@ -51,8 +51,6 @@ public class Application { private List applicationReleases; -// private ApplicationRelease releaseVersion; - private DeviceType devicetype; public int getId() { @@ -105,14 +103,6 @@ public class Application { this.unrestrictedRoles = unrestrictedRoles; } -// public ApplicationRelease getReleaseVersion() { -// return releaseVersion; -// } -// -// public void setReleaseVersion(ApplicationRelease releaseVersion) { -// this.releaseVersion = releaseVersion; -// } - public String getType() { return type; } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationRelease.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationRelease.java index 4f9271f2a6..b46aa0d07d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationRelease.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationRelease.java @@ -82,6 +82,8 @@ public class ApplicationRelease { private int stars; + private String url; + public int getNoOfRatedUsers() { return noOfRatedUsers; } @@ -289,4 +291,12 @@ public class ApplicationRelease { public void setIconLoc(String iconLoc) { this.iconLoc = iconLoc; } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationType.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationType.java index 0a41501cc0..bfcd77a58f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationType.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/ApplicationType.java @@ -23,6 +23,6 @@ package org.wso2.carbon.device.application.mgt.common; * Application Types. */ public enum ApplicationType { - ANDROID, iOS, WEB_CLIP + ANDROID, IOS, WEB_CLIP } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/pom.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/pom.xml index 6dec736627..4fbf25844e 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/pom.xml +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/pom.xml @@ -186,6 +186,16 @@ apk-parser 2.5.2 + + com.googlecode.plist + dd-plist + 1.20 + + + commons-validator + commons-validator + 1.6 + diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/ApplicationStorageManagerImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/ApplicationStorageManagerImpl.java index caa5847071..a4aad2b7bc 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/ApplicationStorageManagerImpl.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/ApplicationStorageManagerImpl.java @@ -19,6 +19,10 @@ package org.wso2.carbon.device.application.mgt.core.impl; +import com.dd.plist.NSDictionary; +import com.dd.plist.NSString; +import com.dd.plist.PropertyListFormatException; +import com.dd.plist.PropertyListParser; import net.dongliu.apk.parser.ApkFile; import net.dongliu.apk.parser.bean.ApkMeta; import org.apache.commons.codec.digest.DigestUtils; @@ -28,20 +32,32 @@ import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.device.application.mgt.common.ApplicationRelease; import org.wso2.carbon.device.application.mgt.common.ApplicationType; -import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException; import org.wso2.carbon.device.application.mgt.common.exception.ApplicationStorageManagementException; import org.wso2.carbon.device.application.mgt.common.exception.ResourceManagementException; import org.wso2.carbon.device.application.mgt.common.services.ApplicationStorageManager; import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil; import org.wso2.carbon.device.application.mgt.core.util.Constants; import org.wso2.carbon.device.application.mgt.core.util.StorageManagementUtil; +import org.apache.commons.validator.routines.UrlValidator; +import org.xml.sax.SAXException; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.text.ParseException; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import javax.xml.parsers.ParserConfigurationException; + +import static org.wso2.carbon.device.application.mgt.core.util.StorageManagementUtil.deleteDir; import static org.wso2.carbon.device.application.mgt.core.util.StorageManagementUtil.saveFile; /** @@ -51,6 +67,7 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager private static final Log log = LogFactory.getLog(ApplicationStorageManagerImpl.class); private String storagePath; private int screenShotMaxCount; + private static final int BUFFER_SIZE = 4096; /** * Create a new ApplicationStorageManager Instance @@ -163,11 +180,30 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager public ApplicationRelease uploadReleaseArtifact(ApplicationRelease applicationRelease, String appType, InputStream binaryFile) throws ResourceManagementException { - String artifactDirectoryPath; - String md5OfApp; - md5OfApp = getMD5(binaryFile); - try { + if (ApplicationType.WEB_CLIP.toString().equals(appType)) { + applicationRelease.setVersion(Constants.DEFAULT_VERSION); + UrlValidator urlValidator = new UrlValidator(); + if (applicationRelease.getUrl() == null || !urlValidator.isValid(applicationRelease.getUrl())) { + throw new ApplicationStorageManagementException("Request payload doesn't contains Web Clip URL " + + "with application release object or Web " + + "Clip URL is invalid"); + //todo if we throw this we must send BAD REQUEST to end user + } + applicationRelease.setAppStoredLoc(applicationRelease.getUrl()); + applicationRelease.setAppHashValue(null); + return applicationRelease; + } + + String artifactDirectoryPath; + String md5OfApp; + md5OfApp = getMD5(binaryFile); + + if (md5OfApp == null) { + throw new ApplicationStorageManagementException( + "Error occurred while md5sum value retrieving process: " + + "application UUID " + applicationRelease.getUuid()); + } if (ApplicationType.ANDROID.toString().equals(appType)) { String prefix = "stream2file"; @@ -184,38 +220,43 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager if (!isTempDelete) { log.error("Temporary created APK file deletion failed"); } - } else if (ApplicationType.iOS.toString().equals(appType)) { - //todo iOS ipa validate - } else if (ApplicationType.WEB_CLIP.toString().equals(appType)) { - //todo Web Clip validate + } else if (ApplicationType.IOS.toString().equals(appType)) { + String prefix = "stream2file"; + String suffix = ".ipa"; + Boolean isTempDelete; + + File tempFile = File.createTempFile(prefix, suffix); + FileOutputStream out = new FileOutputStream(tempFile); + IOUtils.copy(binaryFile, out); + Map plistInfo = getIPAInfo(tempFile); + applicationRelease.setVersion(plistInfo.get("CFBundleVersion")); + isTempDelete = tempFile.delete(); + if (!isTempDelete) { + log.error("Temporary created ipa file deletion failed"); + } } else { throw new ApplicationStorageManagementException("Application Type doesn't match with supporting " + "application types " + applicationRelease.getUuid()); } - if (md5OfApp != null) { - artifactDirectoryPath = storagePath + md5OfApp; - StorageManagementUtil.createArtifactDirectory(artifactDirectoryPath); - if (log.isDebugEnabled()) { - log.debug("Artifact Directory Path for saving the application release related artifacts related with " - + "application UUID " + applicationRelease.getUuid() + " is " + artifactDirectoryPath); - } - - String artifactPath = artifactDirectoryPath + Constants.RELEASE_ARTIFACT; - saveFile(binaryFile, artifactPath); - applicationRelease.setAppStoredLoc(artifactPath); - applicationRelease.setAppHashValue(md5OfApp); - } else { - throw new ApplicationStorageManagementException("Error occurred while md5sum value retrieving process: " + - "application UUID " + applicationRelease.getUuid()); + artifactDirectoryPath = storagePath + md5OfApp; + StorageManagementUtil.createArtifactDirectory(artifactDirectoryPath); + if (log.isDebugEnabled()) { + log.debug("Artifact Directory Path for saving the application release related artifacts related with " + + "application UUID " + applicationRelease.getUuid() + " is " + artifactDirectoryPath); } + + String artifactPath = artifactDirectoryPath + Constants.RELEASE_ARTIFACT; + saveFile(binaryFile, artifactPath); + applicationRelease.setAppStoredLoc(artifactPath); + applicationRelease.setAppHashValue(md5OfApp); + } catch (IOException e) { throw new ApplicationStorageManagementException( "IO Exception while saving the release artifacts in the server for the application UUID " + applicationRelease.getUuid(), e); } - return applicationRelease; } @@ -267,4 +308,130 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager } return md5; } + + private synchronized Map getIPAInfo(File ipaFile) throws ApplicationStorageManagementException { + Map ipaInfo = new HashMap<>(); + + String ipaDirectory = null; + try { + String ipaAbsPath = ipaFile.getAbsolutePath(); + ipaDirectory = new File(ipaAbsPath).getParent(); + + if (new File(ipaDirectory + File.separator + Constants.PAYLOAD).exists()) { + deleteDir(new File(ipaDirectory + File.separator + Constants.PAYLOAD)); + } + + // unzip ipa zip file + unzip(ipaAbsPath, ipaDirectory); + + // fetch app file name, after unzip ipa + String appFileName = ""; + for (File file : Objects.requireNonNull( + new File(ipaDirectory + File.separator + Constants.PAYLOAD).listFiles() + )) { + if (file.toString().endsWith(Constants.APP_EXTENSION)) { + appFileName = new File(file.toString()).getAbsolutePath(); + break; + } + } + + String plistFilePath = appFileName + File.separator + Constants.PLIST_NAME; + + // parse info.plist + File plistFile = new File(plistFilePath); + NSDictionary rootDict; + rootDict = (NSDictionary) PropertyListParser.parse(plistFile); + + // get version + NSString parameter = (NSString) rootDict.objectForKey(Constants.CF_BUNDLE_VERSION); + ipaInfo.put(Constants.CF_BUNDLE_VERSION, parameter.toString()); + + } catch (ParseException e) { + String msg = "Error occurred while parsing the plist data"; + log.error(msg); + throw new ApplicationStorageManagementException(msg, e); + } catch (IOException e) { + String msg = "Error occurred while accessing the ipa file"; + log.error(msg); + throw new ApplicationStorageManagementException(msg, e); + } catch (SAXException | ParserConfigurationException | PropertyListFormatException e) { + log.error(e); + throw new ApplicationStorageManagementException(e.getMessage(), e); + } catch (ApplicationStorageManagementException e) { + String msg = "Error occurred while unzipping the ipa file"; + log.error(msg); + throw new ApplicationStorageManagementException(msg, e); + } finally { + if (ipaDirectory != null) { + // remove unzip folder + deleteDir(new File(ipaDirectory + File.separator + Constants.PAYLOAD)); + } + } + return ipaInfo; + } + + /** + * Extracts a zip file specified by the zipFilePath to a directory specified by + * destDirectory (will be created if does not exists) + * + * @param zipFilePath file path of the zip + * @param destDirectory destination directory path + */ + private void unzip(String zipFilePath, String destDirectory) + throws IOException, ApplicationStorageManagementException { + File destDir = new File(destDirectory); + Boolean isDirCreated; + + if (!destDir.exists()) { + isDirCreated = destDir.mkdir(); + if (!isDirCreated) { + throw new ApplicationStorageManagementException("Directory Creation Is Failed while iOS app vertion " + + "retrieval"); + } + } + + ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); + ZipEntry entry = zipIn.getNextEntry(); + + // iterates over entries in the zip file + while (entry != null) { + String filePath = destDirectory + File.separator + entry.getName(); + + if (!entry.isDirectory()) { + // if the entry is a file, extracts it + extractFile(zipIn, filePath); + } else { + // if the entry is a directory, make the directory + File dir = new File(filePath); + isDirCreated = dir.mkdir(); + if (!isDirCreated) { + throw new ApplicationStorageManagementException( + "Directory Creation Is Failed while iOS app vertion " + + "retrieval"); + } + + } + + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + zipIn.close(); + } + + /** + * Extracts a zip entry (file entry) + * + * @param zipIn zip input stream + * @param filePath file path + */ + private void extractFile(ZipInputStream zipIn, String filePath) throws IOException { + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read; + + while ((read = zipIn.read(bytesIn)) != -1) { + bos.write(bytesIn, 0, read); + } + bos.close(); + } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/util/Constants.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/util/Constants.java index e01914eaf7..a0294c4aad 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/util/Constants.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/util/Constants.java @@ -31,9 +31,11 @@ public class Constants { public static final String DEFAULT_CONFIG_FILE_LOCATION = CarbonUtils.getCarbonConfigDirPath() + File.separator + Constants.APPLICATION_CONFIG_XML_FILE; - //can remove - public static final String PLATFORMS_DEPLOYMENT_DIR_NAME = "platforms"; - public static final String PLATFORM_DEPLOYMENT_EXT = ".xml"; + public static final String DEFAULT_VERSION = "1.0.0"; + public static final String PAYLOAD = "Payload"; + public static final String PLIST_NAME = "Info.plist"; + public static final String CF_BUNDLE_VERSION = "CFBundleVersion"; + public static final String APP_EXTENSION = ".app"; /** * Database types supported by Application Management. diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementAPI.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementAPI.java index c22132d513..69fc0e9a64 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementAPI.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/ApplicationManagementAPI.java @@ -397,7 +397,7 @@ public interface ApplicationManagementAPI { response = ErrorResponse.class) }) Response updateApplicationArtifact( - @ApiParam(name = "appType", value = "Type of the application i.e Android, iOS etc", required = true) + @ApiParam(name = "appType", value = "Type of the application i.e Android, IOS etc", required = true) @PathParam("appType") String appType, @ApiParam(name = "id", value = "Id of the application", required = true) @PathParam("uuid") int applicationId, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementAPIImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementAPIImpl.java index 6ecd44cba7..eaa12feed8 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementAPIImpl.java +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.api/src/main/java/org/wso2/carbon/device/application/mgt/publisher/api/services/impl/ApplicationManagementAPIImpl.java @@ -138,10 +138,16 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI { return Response.status(Response.Status.BAD_REQUEST).build(); } - if (binaryFile == null) { + if (binaryFile == null && !ApplicationType.WEB_CLIP.toString().equals(application.getType())) { log.error("Binary file is not uploaded for the application release of " + application.getName() + " of application type " + application.getType()); return Response.status(Response.Status.BAD_REQUEST).build(); + }else if(binaryFile == null && ApplicationType.WEB_CLIP.toString().equals(application.getType())){ + applicationRelease = applicationStorageManager.uploadReleaseArtifact(applicationRelease, application.getType(), + null); + }else if (binaryFile != null && !ApplicationType.WEB_CLIP.toString().equals(application.getType())){ + applicationRelease = applicationStorageManager.uploadReleaseArtifact(applicationRelease, application.getType(), + binaryFile.getDataHandler().getInputStream()); } iconFileStream = iconFile.getDataHandler().getInputStream(); @@ -151,8 +157,6 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI { attachments.add(screenshot.getDataHandler().getInputStream()); } - applicationRelease = applicationStorageManager.uploadReleaseArtifact(applicationRelease, application.getType(), - binaryFile.getDataHandler().getInputStream()); if (applicationRelease.getAppStoredLoc() == null || applicationRelease.getAppHashValue() == null) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();