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 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;
}

@ -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;
}
}

@ -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
}

@ -186,6 +186,16 @@
<artifactId>apk-parser</artifactId>
<version>2.5.2</version>
</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>
</project>

@ -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 {
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);
try {
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,16 +220,25 @@ 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<String, String> 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()) {
@ -205,17 +250,13 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
saveFile(binaryFile, artifactPath);
applicationRelease.setAppStoredLoc(artifactPath);
applicationRelease.setAppHashValue(md5OfApp);
} else {
throw new ApplicationStorageManagementException("Error occurred while md5sum value retrieving process: " +
"application UUID " + applicationRelease.getUuid());
}
} 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<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 +
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.

@ -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,

@ -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();

Loading…
Cancel
Save