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
feature/appm-store/pbac
lasanthaDLPDS 7 years ago
parent b45d11bfdc
commit c95733a781

@ -51,8 +51,6 @@ public class Application {
private List<ApplicationRelease> applicationReleases; private List<ApplicationRelease> applicationReleases;
// private ApplicationRelease releaseVersion;
private DeviceType devicetype; private DeviceType devicetype;
public int getId() { public int getId() {
@ -105,14 +103,6 @@ public class Application {
this.unrestrictedRoles = unrestrictedRoles; this.unrestrictedRoles = unrestrictedRoles;
} }
// public ApplicationRelease getReleaseVersion() {
// return releaseVersion;
// }
//
// public void setReleaseVersion(ApplicationRelease releaseVersion) {
// this.releaseVersion = releaseVersion;
// }
public String getType() { public String getType() {
return type; return type;
} }

@ -82,6 +82,8 @@ public class ApplicationRelease {
private int stars; private int stars;
private String url;
public int getNoOfRatedUsers() { public int getNoOfRatedUsers() {
return noOfRatedUsers; return noOfRatedUsers;
} }
@ -289,4 +291,12 @@ public class ApplicationRelease {
public void setIconLoc(String iconLoc) { public void setIconLoc(String iconLoc) {
this.iconLoc = iconLoc; this.iconLoc = iconLoc;
} }
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
} }

@ -23,6 +23,6 @@ package org.wso2.carbon.device.application.mgt.common;
* Application Types. * Application Types.
*/ */
public enum ApplicationType { public enum ApplicationType {
ANDROID, iOS, WEB_CLIP ANDROID, IOS, WEB_CLIP
} }

@ -186,6 +186,16 @@
<artifactId>apk-parser</artifactId> <artifactId>apk-parser</artifactId>
<version>2.5.2</version> <version>2.5.2</version>
</dependency> </dependency>
<dependency>
<groupId>com.googlecode.plist</groupId>
<artifactId>dd-plist</artifactId>
<version>1.20</version>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.6</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -19,6 +19,10 @@
package org.wso2.carbon.device.application.mgt.core.impl; 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.ApkFile;
import net.dongliu.apk.parser.bean.ApkMeta; import net.dongliu.apk.parser.bean.ApkMeta;
import org.apache.commons.codec.digest.DigestUtils; 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.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.application.mgt.common.ApplicationRelease; 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.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.ApplicationStorageManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.ResourceManagementException; 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.common.services.ApplicationStorageManager;
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil; 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.Constants;
import org.wso2.carbon.device.application.mgt.core.util.StorageManagementUtil; 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.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List; 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; 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 static final Log log = LogFactory.getLog(ApplicationStorageManagerImpl.class);
private String storagePath; private String storagePath;
private int screenShotMaxCount; private int screenShotMaxCount;
private static final int BUFFER_SIZE = 4096;
/** /**
* Create a new ApplicationStorageManager Instance * Create a new ApplicationStorageManager Instance
@ -163,11 +180,30 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
public ApplicationRelease uploadReleaseArtifact(ApplicationRelease applicationRelease, String appType, InputStream binaryFile) public ApplicationRelease uploadReleaseArtifact(ApplicationRelease applicationRelease, String appType, InputStream binaryFile)
throws ResourceManagementException { throws ResourceManagementException {
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 artifactDirectoryPath;
String md5OfApp; String md5OfApp;
md5OfApp = getMD5(binaryFile); md5OfApp = getMD5(binaryFile);
try { if (md5OfApp == null) {
throw new ApplicationStorageManagementException(
"Error occurred while md5sum value retrieving process: " +
"application UUID " + applicationRelease.getUuid());
}
if (ApplicationType.ANDROID.toString().equals(appType)) { if (ApplicationType.ANDROID.toString().equals(appType)) {
String prefix = "stream2file"; String prefix = "stream2file";
@ -184,16 +220,25 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
if (!isTempDelete) { if (!isTempDelete) {
log.error("Temporary created APK file deletion failed"); log.error("Temporary created APK file deletion failed");
} }
} else if (ApplicationType.iOS.toString().equals(appType)) { } else if (ApplicationType.IOS.toString().equals(appType)) {
//todo iOS ipa validate String prefix = "stream2file";
} else if (ApplicationType.WEB_CLIP.toString().equals(appType)) { String suffix = ".ipa";
//todo Web Clip validate Boolean isTempDelete;
File tempFile = File.createTempFile(prefix, suffix);
FileOutputStream out = new FileOutputStream(tempFile);
IOUtils.copy(binaryFile, out);
Map<String, String> plistInfo = getIPAInfo(tempFile);
applicationRelease.setVersion(plistInfo.get("CFBundleVersion"));
isTempDelete = tempFile.delete();
if (!isTempDelete) {
log.error("Temporary created ipa file deletion failed");
}
} else { } else {
throw new ApplicationStorageManagementException("Application Type doesn't match with supporting " + throw new ApplicationStorageManagementException("Application Type doesn't match with supporting " +
"application types " + applicationRelease.getUuid()); "application types " + applicationRelease.getUuid());
} }
if (md5OfApp != null) {
artifactDirectoryPath = storagePath + md5OfApp; artifactDirectoryPath = storagePath + md5OfApp;
StorageManagementUtil.createArtifactDirectory(artifactDirectoryPath); StorageManagementUtil.createArtifactDirectory(artifactDirectoryPath);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
@ -205,17 +250,13 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
saveFile(binaryFile, artifactPath); saveFile(binaryFile, artifactPath);
applicationRelease.setAppStoredLoc(artifactPath); applicationRelease.setAppStoredLoc(artifactPath);
applicationRelease.setAppHashValue(md5OfApp); applicationRelease.setAppHashValue(md5OfApp);
} else {
throw new ApplicationStorageManagementException("Error occurred while md5sum value retrieving process: " +
"application UUID " + applicationRelease.getUuid());
}
} catch (IOException e) { } catch (IOException e) {
throw new ApplicationStorageManagementException( throw new ApplicationStorageManagementException(
"IO Exception while saving the release artifacts in the server for the application UUID " "IO Exception while saving the release artifacts in the server for the application UUID "
+ applicationRelease.getUuid(), e); + applicationRelease.getUuid(), e);
} }
return applicationRelease; return applicationRelease;
} }
@ -267,4 +308,130 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
} }
return md5; return md5;
} }
private synchronized Map<String, String> getIPAInfo(File ipaFile) throws ApplicationStorageManagementException {
Map<String, String> 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();
}
} }

@ -31,9 +31,11 @@ public class Constants {
public static final String DEFAULT_CONFIG_FILE_LOCATION = CarbonUtils.getCarbonConfigDirPath() + File.separator + public static final String DEFAULT_CONFIG_FILE_LOCATION = CarbonUtils.getCarbonConfigDirPath() + File.separator +
Constants.APPLICATION_CONFIG_XML_FILE; Constants.APPLICATION_CONFIG_XML_FILE;
//can remove public static final String DEFAULT_VERSION = "1.0.0";
public static final String PLATFORMS_DEPLOYMENT_DIR_NAME = "platforms"; public static final String PAYLOAD = "Payload";
public static final String PLATFORM_DEPLOYMENT_EXT = ".xml"; 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. * Database types supported by Application Management.

@ -397,7 +397,7 @@ public interface ApplicationManagementAPI {
response = ErrorResponse.class) response = ErrorResponse.class)
}) })
Response updateApplicationArtifact( 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, @PathParam("appType") String appType,
@ApiParam(name = "id", value = "Id of the application", required = true) @ApiParam(name = "id", value = "Id of the application", required = true)
@PathParam("uuid") int applicationId, @PathParam("uuid") int applicationId,

@ -138,10 +138,16 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI {
return Response.status(Response.Status.BAD_REQUEST).build(); 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() + log.error("Binary file is not uploaded for the application release of " + application.getName() +
" of application type " + application.getType()); " of application type " + application.getType());
return Response.status(Response.Status.BAD_REQUEST).build(); 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(); iconFileStream = iconFile.getDataHandler().getInputStream();
@ -151,8 +157,6 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI {
attachments.add(screenshot.getDataHandler().getInputStream()); attachments.add(screenshot.getDataHandler().getInputStream());
} }
applicationRelease = applicationStorageManager.uploadReleaseArtifact(applicationRelease, application.getType(),
binaryFile.getDataHandler().getInputStream());
if (applicationRelease.getAppStoredLoc() == null || applicationRelease.getAppHashValue() == null) { if (applicationRelease.getAppStoredLoc() == null || applicationRelease.getAppHashValue() == null) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();

Loading…
Cancel
Save