From 37981340118e30929082e98008a1ab3d62ae1294 Mon Sep 17 00:00:00 2001 From: Rajitha Kumara Date: Mon, 12 Feb 2024 10:36:58 +0530 Subject: [PATCH] Add stream base file uploading --- .../mgt/common/ChunkDescriptor.java | 52 +++ .../mgt/common/FileDescriptor.java | 79 +++++ .../application/mgt/common/FileMetaEntry.java | 60 ++++ .../application/mgt/common/TransferLink.java | 108 +++++++ .../FileDownloaderServiceException.java | 30 ++ .../FileTransferServiceException.java | 31 ++ .../common/services/ApplicationManager.java | 4 +- .../services/FileDownloaderService.java | 29 ++ .../common/services/FileTransferService.java | 73 +++++ .../wrapper/CustomAppReleaseWrapper.java | 46 +++ .../common/wrapper/EntAppReleaseWrapper.java | 46 +++ .../wrapper/PublicAppReleaseWrapper.java | 36 +++ .../common/wrapper/TransferLinkWrapper.java | 41 +++ .../common/wrapper/WebAppReleaseWrapper.java | 36 +++ .../pom.xml | 14 +- ...ileTransferServiceHelperUtilException.java | 30 ++ .../mgt/core/impl/ApplicationManagerImpl.java | 303 ++++++++++++++---- .../impl/FileDownloaderServiceProvider.java | 147 +++++++++ .../core/impl/FileTransferServiceImpl.java | 124 +++++++ ...ApplicationManagementServiceComponent.java | 6 + .../mgt/core/internal/DataHolder.java | 10 + .../application/mgt/core/util/APIUtil.java | 13 + .../core/util/ApplicationManagementUtil.java | 169 +++++++--- .../util/FileTransferServiceHelperUtil.java | 236 ++++++++++++++ .../management/ApplicationManagementTest.java | 89 +++-- .../src/main/resources/conf/mdm-ui-config.xml | 1 + pom.xml | 2 +- 27 files changed, 1693 insertions(+), 122 deletions(-) create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/ChunkDescriptor.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileDescriptor.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileMetaEntry.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/TransferLink.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileDownloaderServiceException.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileTransferServiceException.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileDownloaderService.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileTransferService.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/TransferLinkWrapper.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/exception/FileTransferServiceHelperUtilException.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileDownloaderServiceProvider.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileTransferServiceImpl.java create mode 100644 components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/FileTransferServiceHelperUtil.java diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/ChunkDescriptor.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/ChunkDescriptor.java new file mode 100644 index 0000000000..d5c23e20ee --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/ChunkDescriptor.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common; + +import java.io.InputStream; + +public class ChunkDescriptor { + private FileDescriptor associateFileDescriptor; + private long size; + private InputStream chunk; + + public FileDescriptor getAssociateFileDescriptor() { + return associateFileDescriptor; + } + + public void setAssociateFileDescriptor(FileDescriptor associateFileDescriptor) { + this.associateFileDescriptor = associateFileDescriptor; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public InputStream getChunk() { + return chunk; + } + + public void setChunk(InputStream chunk) { + this.chunk = chunk; + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileDescriptor.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileDescriptor.java new file mode 100644 index 0000000000..226d8cb759 --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileDescriptor.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common; + +import java.io.InputStream; + +public class FileDescriptor { + private String fileName; + private String extension; + private String fullQualifiedName; + private String absolutePath; + private long actualFileSize; + private InputStream file; + + public InputStream getFile() { + return file; + } + + public void setFile(InputStream file) { + this.file = file; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getExtension() { + return extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getAbsolutePath() { + return absolutePath; + } + + public void setAbsolutePath(String absolutePath) { + this.absolutePath = absolutePath; + } + + public long getActualFileSize() { + return actualFileSize; + } + + public void setActualFileSize(long actualFileSize) { + this.actualFileSize = actualFileSize; + } + + public String getFullQualifiedName() { + return fullQualifiedName; + } + + public void setFullQualifiedName(String fullQualifiedName) { + this.fullQualifiedName = fullQualifiedName; + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileMetaEntry.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileMetaEntry.java new file mode 100644 index 0000000000..506ee3133f --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/FileMetaEntry.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common; + +public class FileMetaEntry { + private String fileName; + private String extension; + + private long size; + private String absolutePath; + + public String getAbsolutePath() { + return absolutePath; + } + + public void setAbsolutePath(String absolutePath) { + this.absolutePath = absolutePath; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getExtension() { + return extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/TransferLink.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/TransferLink.java new file mode 100644 index 0000000000..d6d742637a --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/TransferLink.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common; + +import java.util.Objects; + +public class TransferLink { + private static final String SCHEMA_SEPARATOR = "://"; + private static final String URL_SEPARATOR = "/"; + private static final String COLON = ":"; + private final String schema; + private final String host; + private final String port; + private final String endpoint; + private final String artifactHolderUUID; + + private TransferLink(String schema, String host, String port, String endpoint, String artifactHolderUUID) { + this.schema = schema; + this.host = host; + this.port = port; + this.endpoint = endpoint; + this.artifactHolderUUID = artifactHolderUUID; + } + + public String getDirectTransferLink() { + return schema + SCHEMA_SEPARATOR + host + COLON + port + URL_SEPARATOR + endpoint + URL_SEPARATOR + artifactHolderUUID; + } + + public String getRelativeTransferLink() { + return endpoint + URL_SEPARATOR + artifactHolderUUID; + } + + @Override + public String toString() { + return getDirectTransferLink(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransferLink that = (TransferLink) o; + return Objects.equals(schema, that.schema) && Objects.equals(host, that.host) && Objects.equals(port, that.port) + && Objects.equals(endpoint, that.endpoint) && Objects.equals(artifactHolderUUID, that.artifactHolderUUID); + } + + @Override + public int hashCode() { + return Objects.hash(schema, host, port, endpoint, artifactHolderUUID); + } + + public static class TransferLinkBuilder { + private static final String DEFAULT_SCHEMA = "https"; + private static final String ENDPOINT = "application-mgt-publisher/v1.0/applications/uploads"; + private static final String IOT_GW_HOST_ENV_VAR = "iot.gateway.host"; + private static final String IOT_GW_HTTPS_PORT_ENV_VAR = "iot.gateway.https.port"; + private static final String IOT_GW_HTTP_PORT_ENV_VAR = "iot.gateway.http.port"; + private String schema; + private String endpoint; + private final String artifactHolderUUID; + + public TransferLinkBuilder(String artifactHolderUUID) { + this.schema = DEFAULT_SCHEMA; + this.endpoint = ENDPOINT; + this.artifactHolderUUID = artifactHolderUUID; + } + + public TransferLinkBuilder withSchema(String schema) { + this.schema = schema; + return this; + } + + public TransferLinkBuilder withEndpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + + public TransferLink build() { + return new TransferLink(this.schema, resolveHost(), resolvePort(), this.endpoint, this.artifactHolderUUID); + } + + private String resolveHost() { + return System.getProperty(IOT_GW_HOST_ENV_VAR); + } + + private String resolvePort() { + return Objects.equals(this.schema, DEFAULT_SCHEMA) ? System.getProperty(IOT_GW_HTTPS_PORT_ENV_VAR) + : System.getProperty(IOT_GW_HTTP_PORT_ENV_VAR); + } + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileDownloaderServiceException.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileDownloaderServiceException.java new file mode 100644 index 0000000000..c51624939f --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileDownloaderServiceException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common.exception; + +public class FileDownloaderServiceException extends Exception { + public FileDownloaderServiceException(String msg) { + super(msg); + } + + public FileDownloaderServiceException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileTransferServiceException.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileTransferServiceException.java new file mode 100644 index 0000000000..b2ce0aa8ba --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/exception/FileTransferServiceException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common.exception; + +public class FileTransferServiceException extends Exception { + public FileTransferServiceException(String msg) { + super(msg); + } + + public FileTransferServiceException(String msg, Throwable throwable) { + super(msg, throwable); + } + +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java index 57890f53eb..b46fe7a562 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/ApplicationManager.java @@ -383,11 +383,9 @@ public interface ApplicationManager { * * @param releaseUuid UUID of the application release. * @param publicAppReleaseWrapper {@link ApplicationReleaseDTO} - * @param applicationArtifact {@link ApplicationArtifact} * @return If the application release is updated correctly True returns, otherwise retuen False */ - ApplicationRelease updatePubAppRelease(String releaseUuid, PublicAppReleaseWrapper publicAppReleaseWrapper, - ApplicationArtifact applicationArtifact) throws ApplicationManagementException; + ApplicationRelease updatePubAppRelease(String releaseUuid, PublicAppReleaseWrapper publicAppReleaseWrapper) throws ApplicationManagementException; /** * Use to update existing web app release diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileDownloaderService.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileDownloaderService.java new file mode 100644 index 0000000000..fae56ab2d3 --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileDownloaderService.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common.services; + +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileDownloaderServiceException; + +import java.net.URL; + +public interface FileDownloaderService { + FileDescriptor download(URL downloadUrl) throws FileDownloaderServiceException; +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileTransferService.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileTransferService.java new file mode 100644 index 0000000000..1a69cf8e75 --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/services/FileTransferService.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common.services; + +import io.entgra.device.mgt.core.application.mgt.common.ChunkDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileMetaEntry; +import io.entgra.device.mgt.core.application.mgt.common.TransferLink; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileTransferServiceException; +import io.entgra.device.mgt.core.device.mgt.common.exceptions.NotFoundException; + +import java.io.InputStream; +import java.net.URL; + +public interface FileTransferService { + /** + * Create an upload link + * @param fileMetaEntry {@link FileMetaEntry} + * @return {@link TransferLink} + * @throws FileTransferServiceException Throws when error encountered while generating upload link + */ + TransferLink generateUploadLink(FileMetaEntry fileMetaEntry) throws FileTransferServiceException; + + /** + * Resolve {@link ChunkDescriptor} using artifactHolder UUID and a given chunk + * @param artifactHolder Artifact holder's UUID string + * @param chunk Data chunk + * @return {@link ChunkDescriptor} + * @throws FileTransferServiceException Throws when error encountered while resolving chunk descriptor + * @throws NotFoundException Throws when artifact holder not exists in the file system + */ + ChunkDescriptor resolve(String artifactHolder, InputStream chunk) throws FileTransferServiceException, NotFoundException; + + /** + * Write chunk of data + * @param chunkDescriptor {@link ChunkDescriptor} + * @throws FileTransferServiceException Throws when error encountered while writing chunk + */ + void writeChunk(ChunkDescriptor chunkDescriptor) throws FileTransferServiceException; + + /** + * Check if the provided download url point to a file which exists on the local env or not + * @param downloadUrl Download URL + * @return Returns true if the download URL point to a file which resides in local + * @throws FileTransferServiceException Throws when error encountered while checking + */ + boolean isExistsOnLocal(URL downloadUrl) throws FileTransferServiceException; + + /** + * Resolve {@link FileDescriptor} from a given download URL + * @param downloadUrl Download URL + * @return {@link java.io.FileDescriptor} + * @throws FileTransferServiceException Throws when error encountered while resolving file descriptor + */ + FileDescriptor resolve(URL downloadUrl) throws FileTransferServiceException; +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/CustomAppReleaseWrapper.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/CustomAppReleaseWrapper.java index af4d6b6dbd..71d210eeee 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/CustomAppReleaseWrapper.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/CustomAppReleaseWrapper.java @@ -83,6 +83,44 @@ public class CustomAppReleaseWrapper { @ApiModelProperty(name = "icon", value = "banner of the application") private Base64File banner; + private boolean remoteStatus; + + public boolean isRemoteStatus() { + return remoteStatus; + } + + public void setRemoteStatus(boolean remoteStatus) { + this.remoteStatus = remoteStatus; + } + + private String artifactLink; + private List screenshotLinks; + private String iconLink; + private String bannerLink; + + public String getArtifactLink() { + return artifactLink; + } + + public void setArtifactLink(String artifactLink) { + this.artifactLink = artifactLink; + } + + public List getScreenshotLinks() { + return screenshotLinks; + } + + public void setScreenshotLinks(List screenshotLinks) { + this.screenshotLinks = screenshotLinks; + } + + public String getIconLink() { + return iconLink; + } + + public void setIconLink(String iconLink) { + this.iconLink = iconLink; + } public String getReleaseType() { return releaseType; @@ -173,4 +211,12 @@ public class CustomAppReleaseWrapper { public void setBanner(Base64File banner) { this.banner = banner; } + + public String getBannerLink() { + return bannerLink; + } + + public void setBannerLink(String bannerLink) { + this.bannerLink = bannerLink; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/EntAppReleaseWrapper.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/EntAppReleaseWrapper.java index 91ad28aae5..1a77c6d0be 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/EntAppReleaseWrapper.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/EntAppReleaseWrapper.java @@ -86,6 +86,44 @@ public class EntAppReleaseWrapper { @ApiModelProperty(name = "icon", value = "banner of the application") private Base64File banner; + private boolean remoteStatus; + + public boolean isRemoteStatus() { + return remoteStatus; + } + + public void setRemoteStatus(boolean remoteStatus) { + this.remoteStatus = remoteStatus; + } + + private String artifactLink; + private List screenshotLinks; + private String iconLink; + private String bannerLink; + + public String getArtifactLink() { + return artifactLink; + } + + public void setArtifactLink(String artifactLink) { + this.artifactLink = artifactLink; + } + + public List getScreenshotLinks() { + return screenshotLinks; + } + + public void setScreenshotLinks(List screenshotLinks) { + this.screenshotLinks = screenshotLinks; + } + + public String getIconLink() { + return iconLink; + } + + public void setIconLink(String iconLink) { + this.iconLink = iconLink; + } public String getReleaseType() { return releaseType; @@ -174,4 +212,12 @@ public class EntAppReleaseWrapper { public void setBanner(Base64File banner) { this.banner = banner; } + + public String getBannerLink() { + return bannerLink; + } + + public void setBannerLink(String bannerLink) { + this.bannerLink = bannerLink; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/PublicAppReleaseWrapper.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/PublicAppReleaseWrapper.java index 0bc46c6dbd..9962ff4000 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/PublicAppReleaseWrapper.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/PublicAppReleaseWrapper.java @@ -86,6 +86,18 @@ public class PublicAppReleaseWrapper { @ApiModelProperty(name = "icon", value = "banner of the application") private Base64File banner; + private boolean remoteStatus; + + public boolean isRemoteStatus() { + return remoteStatus; + } + + public void setRemoteStatus(boolean remoteStatus) { + this.remoteStatus = remoteStatus; + } + private List screenshotLinks; + private String iconLink; + private String bannerLink; public String getReleaseType() { return releaseType; @@ -162,4 +174,28 @@ public class PublicAppReleaseWrapper { public void setBanner(Base64File banner) { this.banner = banner; } + + public List getScreenshotLinks() { + return screenshotLinks; + } + + public void setScreenshotLinks(List screenshotLinks) { + this.screenshotLinks = screenshotLinks; + } + + public String getIconLink() { + return iconLink; + } + + public void setIconLink(String iconLink) { + this.iconLink = iconLink; + } + + public String getBannerLink() { + return bannerLink; + } + + public void setBannerLink(String bannerLink) { + this.bannerLink = bannerLink; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/TransferLinkWrapper.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/TransferLinkWrapper.java new file mode 100644 index 0000000000..41d9b4c25c --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/TransferLinkWrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.common.wrapper; + +public class TransferLinkWrapper { + private String directTransferLink; + private String relativeTransferLink; + + public String getDirectTransferLink() { + return directTransferLink; + } + + public void setDirectTransferLink(String directTransferLink) { + this.directTransferLink = directTransferLink; + } + + public String getRelativeTransferLink() { + return relativeTransferLink; + } + + public void setRelativeTransferLink(String relativeTransferLink) { + this.relativeTransferLink = relativeTransferLink; + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/WebAppReleaseWrapper.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/WebAppReleaseWrapper.java index 10b3d75249..2eae49742c 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/WebAppReleaseWrapper.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.common/src/main/java/io/entgra/device/mgt/core/application/mgt/common/wrapper/WebAppReleaseWrapper.java @@ -77,6 +77,42 @@ public class WebAppReleaseWrapper { @ApiModelProperty(name = "icon", value = "banner of the application") private Base64File banner; + private boolean remoteStatus; + + public boolean isRemoteStatus() { + return remoteStatus; + } + + public void setRemoteStatus(boolean remoteStatus) { + this.remoteStatus = remoteStatus; + } + private List screenshotLinks; + private String iconLink; + private String bannerLink; + + public List getScreenshotLinks() { + return screenshotLinks; + } + + public void setScreenshotLinks(List screenshotLinks) { + this.screenshotLinks = screenshotLinks; + } + + public String getIconLink() { + return iconLink; + } + + public void setIconLink(String iconLink) { + this.iconLink = iconLink; + } + + public String getBannerLink() { + return bannerLink; + } + + public void setBannerLink(String bannerLink) { + this.bannerLink = bannerLink; + } public String getReleaseType() { return releaseType; diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml index c1ae3a7bb5..f94823e8f7 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/pom.xml @@ -83,7 +83,10 @@ io.entgra.device.mgt.core.apimgt.application.extension.*, org.apache.commons.httpclient, org.apache.commons.httpclient.methods, - org.apache.commons.validator.routines + org.apache.commons.validator.routines, + okhttp3.OkHttpClient, + okhttp3.Request, + okhttp3.Response apk-parser;scope=compile|runtime;inline=false @@ -105,6 +108,9 @@ ${basedir}/target/coverage-reports/jacoco-unit.exec file:src/test/resources/log4j.properties + 8280 + 8280 + test @@ -112,7 +118,6 @@ - org.apache.httpcomponents httpclient @@ -367,6 +372,11 @@ org.wso2.carbon.ntask.core provided + + com.squareup.okhttp3 + okhttp + compile + diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/exception/FileTransferServiceHelperUtilException.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/exception/FileTransferServiceHelperUtilException.java new file mode 100644 index 0000000000..4dd05689cb --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/exception/FileTransferServiceHelperUtilException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.core.exception; + +public class FileTransferServiceHelperUtilException extends Exception { + public FileTransferServiceHelperUtilException(String msg) { + super(msg); + } + + public FileTransferServiceHelperUtilException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java index 3b2eb638aa..98bebe80cf 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/ApplicationManagerImpl.java @@ -18,11 +18,14 @@ package io.entgra.device.mgt.core.application.mgt.core.impl; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileDownloaderServiceException; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileTransferServiceException; import io.entgra.device.mgt.core.application.mgt.core.exception.BadRequestException; import io.entgra.device.mgt.core.device.mgt.common.Base64File; import io.entgra.device.mgt.core.application.mgt.core.dao.SPApplicationDAO; import io.entgra.device.mgt.core.application.mgt.core.util.ApplicationManagementUtil; import io.entgra.device.mgt.core.device.mgt.common.PaginationRequest; +import io.entgra.device.mgt.core.device.mgt.common.app.mgt.App; import io.entgra.device.mgt.core.device.mgt.common.exceptions.MetadataManagementException; import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.Metadata; import org.apache.commons.codec.digest.DigestUtils; @@ -103,6 +106,8 @@ import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -145,6 +150,55 @@ public class ApplicationManagerImpl implements ApplicationManager { @Override public Application createApplication(T app, boolean isPublished) throws ApplicationManagementException { + return createApplicationBasedOnRemoteStatus(app, isPublished); + } + + /** + * Create the application based on the release wrapper's remote status. If the remote status is true, then + * the application creation will take place asynchronously. + * @param app Application release wrapper + * @param isPublished Publish status + * @return {@link Application} + * @throws ApplicationManagementException Throws when error occurred while application creation + */ + @SuppressWarnings("unchecked") + private Application createApplicationBasedOnRemoteStatus(T app, boolean isPublished) throws ApplicationManagementException { + if (ApplicationManagementUtil.getRemoteStatus(app)) { + List releaseWrappers = ApplicationManagementUtil.deriveApplicationWithoutRelease(app); + Application createdApplication = triggerApplicationCreation(app, isPublished); + if (createdApplication == null) { + throw new ApplicationManagementException("Null retrieved for created application."); + } + try { + if (releaseWrappers != null && !releaseWrappers.isEmpty()) { + if (app instanceof ApplicationWrapper) { + ((ApplicationWrapper) app).setEntAppReleaseWrappers((List) releaseWrappers); + createApplicationReleaseBasedOnRemoteStatus(createdApplication.getId(), + ((ApplicationWrapper) app).getEntAppReleaseWrappers().get(0), isPublished); + } else if (app instanceof CustomAppWrapper) { + ((CustomAppWrapper) app).setCustomAppReleaseWrappers((List) releaseWrappers); + createApplicationReleaseBasedOnRemoteStatus(createdApplication.getId(), + ((CustomAppWrapper) app).getCustomAppReleaseWrappers().get(0), isPublished); + } else { + throw new ApplicationManagementException("Unsupported release wrapper received"); + } + } + return createdApplication; + } catch (ResourceManagementException e) { + throw new ApplicationManagementException("Error encountered while creating deploying artifact", e); + } + } + return triggerApplicationCreation(app, isPublished); + } + + /** + * Trigger the application creation process + * @param app Application release wrapper + * @param isPublished Publish status + * @return {@link Application} + * @throws ApplicationManagementException Throws when error occurred while creating the application + */ + private Application triggerApplicationCreation(T app, boolean isPublished) throws ApplicationManagementException { ApplicationDTO applicationDTO = uploadReleaseArtifactIfExist(app); try { ConnectionManagerUtil.beginDBTransaction(); @@ -172,22 +226,110 @@ public class ApplicationManagerImpl implements ApplicationManager { } } + /** + * Create application release based on remote status. If the remote status is true, then the + * application release creation will take place asynchronously. + * @param appId Application id + * @param releaseWrapper Release wrapper + * @param isPublished Publish status + * @return {@link Application} + * @throws ApplicationManagementException Throws when error occurred while deploying the release + * @throws ResourceManagementException Throws when error occurred while deploying the release + */ + private ApplicationRelease createApplicationReleaseBasedOnRemoteStatus(int appId, T releaseWrapper, boolean isPublished) + throws ApplicationManagementException, ResourceManagementException { + if (ApplicationManagementUtil.getRemoteStatusFromWrapper(releaseWrapper)) { + triggerReleaseAsynchronously(appId, releaseWrapper, isPublished); + } else { + if (releaseWrapper instanceof EntAppReleaseWrapper) { + return triggerEntAppRelease(appId, (EntAppReleaseWrapper) releaseWrapper, isPublished); + } + + if (releaseWrapper instanceof CustomAppReleaseWrapper) { + return triggerCustomAppRelease(appId, (CustomAppReleaseWrapper) releaseWrapper, isPublished); + } + + throw new ApplicationManagementException("Unsupported release wrapper received"); + } + return new ApplicationRelease(); + } + + /** + * Trigger release creation asynchronously + * @param appId Application id + * @param releaseWrapper Release wrapper + * @param isPublished Publish status + */ + private void triggerReleaseAsynchronously(int appId, T releaseWrapper, boolean isPublished) { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + new Thread(() -> { + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId, true); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(username); + if (releaseWrapper instanceof EntAppReleaseWrapper && + ((EntAppReleaseWrapper) releaseWrapper).isRemoteStatus()) { + triggerEntAppRelease(appId, (EntAppReleaseWrapper) releaseWrapper, isPublished); + }else if (releaseWrapper instanceof CustomAppReleaseWrapper && + ((CustomAppReleaseWrapper) releaseWrapper).isRemoteStatus()) { + triggerCustomAppRelease(appId, (CustomAppReleaseWrapper) releaseWrapper, isPublished); + } else { + throw new ApplicationManagementException("Unsupported release wrapper received"); + } + } catch (ApplicationManagementException | ResourceManagementException e) { + log.error("Error encountered while deploying remote application release", e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + }).start(); + } + + /** + * Trigger enterprise application creation + * @param appId Application id + * @param releaseWrapper Release wrapper + * @param isPublished Publish status + * @return {@link ApplicationRelease} + * @throws ApplicationManagementException Throws when error encountered while creating enterprise application + */ + private ApplicationRelease triggerEntAppRelease(int appId, EntAppReleaseWrapper releaseWrapper, boolean isPublished) + throws ApplicationManagementException{ + ApplicationManager applicationManager = APIUtil.getApplicationManager(); + try { + ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + releaseWrapper.getArtifactLink(), releaseWrapper.getBannerLink()); + ApplicationDTO applicationDTO = applicationManager.getApplication(appId); + DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); + ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); + releaseDTO = uploadEntAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName(), true); + try { + return createRelease(applicationDTO, releaseDTO, ApplicationType.ENTERPRISE, isPublished); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while creating ent app release for application with the name: " + applicationDTO.getName(); + log.error(msg, e); + deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); + throw new ApplicationManagementException(msg, e); + } + } catch (MalformedURLException e) { + String msg = "Malformed URL link received as a downloadable link"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (FileDownloaderServiceException e) { + String msg = "Error encountered while downloading application release artifacts for app id " + appId; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } + } + @Override public ApplicationRelease createEntAppRelease(int appId, EntAppReleaseWrapper releaseWrapper, boolean isPublished) throws ApplicationManagementException { - ApplicationManager applicationManager = APIUtil.getApplicationManager(); - ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), releaseWrapper.getScreenshots(), - releaseWrapper.getBinaryFile(), releaseWrapper.getBanner()); - ApplicationDTO applicationDTO = applicationManager.getApplication(appId); - DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); - ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); - releaseDTO = uploadEntAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName(), true); try { - return createRelease(applicationDTO, releaseDTO, ApplicationType.ENTERPRISE, isPublished); - } catch (ApplicationManagementException e) { - String msg = "Error occurred while creating ent app release for application with the name: " + applicationDTO.getName(); + return createApplicationReleaseBasedOnRemoteStatus(appId, releaseWrapper, isPublished); + } catch (ResourceManagementException e) { + String msg = "Error occurred while creating enterprise app release for the app id " + appId; log.error(msg, e); - deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); throw new ApplicationManagementException(msg, e); } } @@ -196,17 +338,27 @@ public class ApplicationManagerImpl implements ApplicationManager { public ApplicationRelease createWebAppRelease(int appId, WebAppReleaseWrapper releaseWrapper, boolean isPublished) throws ApplicationManagementException, ResourceManagementException { ApplicationManager applicationManager = APIUtil.getApplicationManager(); - ApplicationDTO applicationDTO = applicationManager.getApplication(appId); - ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), null, releaseWrapper.getBanner()); - ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); - releaseDTO = uploadWebAppReleaseArtifacts(releaseDTO, artifact); try { - return createRelease(applicationDTO, releaseDTO, ApplicationType.WEB_CLIP, isPublished); - } catch (ApplicationManagementException e) { - String msg = "Error occurred while creating web app release for application with the name: " + applicationDTO.getName(); + ApplicationDTO applicationDTO = applicationManager.getApplication(appId); + ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + null, releaseWrapper.getBannerLink()); + ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); + releaseDTO = uploadWebAppReleaseArtifacts(releaseDTO, artifact); + try { + return createRelease(applicationDTO, releaseDTO, ApplicationType.WEB_CLIP, isPublished); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while creating web app release for application with the name: " + applicationDTO.getName(); + log.error(msg, e); + deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); + throw new ApplicationManagementException(msg, e); + } + } catch (MalformedURLException e) { + String msg = "Malformed URL link received as a downloadable link"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (FileDownloaderServiceException e) { + String msg = "Error encountered while downloading application release artifacts"; log.error(msg, e); - deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); throw new ApplicationManagementException(msg, e); } } @@ -215,38 +367,69 @@ public class ApplicationManagerImpl implements ApplicationManager { public ApplicationRelease createPubAppRelease(int appId, PublicAppReleaseWrapper releaseWrapper, boolean isPublished) throws ResourceManagementException, ApplicationManagementException { ApplicationManager applicationManager = APIUtil.getApplicationManager(); - ApplicationDTO applicationDTO = applicationManager.getApplication(appId); - DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); - ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), null, releaseWrapper.getBanner()); - ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); - releaseDTO = uploadPubAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName()); try { - return createRelease(applicationDTO, releaseDTO, ApplicationType.PUBLIC, isPublished); - } catch (ApplicationManagementException e) { - String msg = "Error occurred while creating ent public release for application with the name: " + applicationDTO.getName(); + ApplicationDTO applicationDTO = applicationManager.getApplication(appId); + DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); + ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + null, releaseWrapper.getBannerLink()); + ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); + releaseDTO = uploadPubAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName()); + try { + return createRelease(applicationDTO, releaseDTO, ApplicationType.PUBLIC, isPublished); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while creating ent public release for application with the name: " + applicationDTO.getName(); + log.error(msg, e); + deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); + throw new ApplicationManagementException(msg, e); + } + } catch (MalformedURLException e) { + String msg = "Malformed URL link received as a downloadable link"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (FileDownloaderServiceException e) { + String msg = "Error encountered while downloading application release artifacts"; log.error(msg, e); - deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); throw new ApplicationManagementException(msg, e); } } @Override public ApplicationRelease createCustomAppRelease(int appId, CustomAppReleaseWrapper releaseWrapper, boolean isPublished) + throws ApplicationManagementException { + try { + return createApplicationReleaseBasedOnRemoteStatus(appId, releaseWrapper, isPublished); + } catch (ResourceManagementException e) { + String msg = "Error occurred while creating enterprise app release"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } + } + + private ApplicationRelease triggerCustomAppRelease(int appId, CustomAppReleaseWrapper releaseWrapper, boolean isPublished) throws ResourceManagementException, ApplicationManagementException { ApplicationManager applicationManager = APIUtil.getApplicationManager(); - ApplicationDTO applicationDTO = applicationManager.getApplication(appId); - DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); - ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), releaseWrapper.getBinaryFile(), releaseWrapper.getBanner()); - ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); - releaseDTO = uploadCustomAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName()); try { - return createRelease(applicationDTO, releaseDTO, ApplicationType.CUSTOM, isPublished); - } catch (ApplicationManagementException e) { - String msg = "Error occurred while creating custom app release for application with the name: " + applicationDTO.getName(); + ApplicationDTO applicationDTO = applicationManager.getApplication(appId); + DeviceType deviceType = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); + ApplicationArtifact artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + releaseWrapper.getArtifactLink(), releaseWrapper.getBannerLink()); + ApplicationReleaseDTO releaseDTO = APIUtil.releaseWrapperToReleaseDTO(releaseWrapper); + releaseDTO = uploadCustomAppReleaseArtifacts(releaseDTO, artifact, deviceType.getName()); + try { + return createRelease(applicationDTO, releaseDTO, ApplicationType.CUSTOM, isPublished); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while creating custom app release for application with the name: " + applicationDTO.getName(); + log.error(msg, e); + deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); + throw new ApplicationManagementException(msg, e); + } + } catch (MalformedURLException e) { + String msg = "Malformed URL link received as a downloadable link"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (FileDownloaderServiceException e) { + String msg = "Error encountered while downloading application release artifacts"; log.error(msg, e); - deleteApplicationArtifacts(Collections.singletonList(releaseDTO.getAppHashValue())); throw new ApplicationManagementException(msg, e); } } @@ -279,27 +462,27 @@ public class ApplicationManagerImpl implements ApplicationManager { if (app instanceof ApplicationWrapper) { ApplicationWrapper wrapper = (ApplicationWrapper) app; EntAppReleaseWrapper releaseWrapper = wrapper.getEntAppReleaseWrappers().get(0); - artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), releaseWrapper.getBinaryFile(), releaseWrapper.getBanner()); + artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + releaseWrapper.getArtifactLink(), releaseWrapper.getBannerLink()); releaseDTO = uploadEntAppReleaseArtifacts(releaseDTO, artifact, wrapper.getDeviceType(), false); } else if (app instanceof PublicAppWrapper) { PublicAppWrapper wrapper = (PublicAppWrapper) app; PublicAppReleaseWrapper releaseWrapper = wrapper.getPublicAppReleaseWrappers().get(0); - artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), null, releaseWrapper.getBanner()); + artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + null, releaseWrapper.getBannerLink()); releaseDTO = uploadPubAppReleaseArtifacts(releaseDTO, artifact, wrapper.getDeviceType()); } else if (app instanceof WebAppWrapper) { WebAppWrapper wrapper = (WebAppWrapper) app; WebAppReleaseWrapper releaseWrapper = wrapper.getWebAppReleaseWrappers().get(0); - artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), null, releaseWrapper.getBanner()); + artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + null, releaseWrapper.getBannerLink()); releaseDTO = uploadWebAppReleaseArtifacts(releaseDTO, artifact); } else if (app instanceof CustomAppWrapper) { CustomAppWrapper wrapper = (CustomAppWrapper) app; CustomAppReleaseWrapper releaseWrapper = wrapper.getCustomAppReleaseWrappers().get(0); - artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIcon(), - releaseWrapper.getScreenshots(), releaseWrapper.getBinaryFile(), releaseWrapper.getBanner()); + artifact = ApplicationManagementUtil.constructApplicationArtifact(releaseWrapper.getIconLink(), releaseWrapper.getScreenshotLinks(), + releaseWrapper.getArtifactLink(), releaseWrapper.getBannerLink()); try { releaseDTO = uploadCustomAppReleaseArtifacts(releaseDTO, artifact, wrapper.getDeviceType()); } catch (ResourceManagementException e) { @@ -316,6 +499,14 @@ public class ApplicationManagerImpl implements ApplicationManager { String msg = "Error Occurred when uploading artifacts of the web clip: " + applicationDTO.getName(); log.error(msg); throw new ApplicationManagementException(msg, e); + } catch (MalformedURLException e) { + String msg = "Malformed URL link received as a downloadable link"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (FileDownloaderServiceException e) { + String msg = "Error encountered while downloading application release artifacts"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); } applicationDTO.getApplicationReleaseDTOs().clear(); applicationDTO.getApplicationReleaseDTOs().add(releaseDTO); @@ -3210,11 +3401,11 @@ public class ApplicationManagerImpl implements ApplicationManager { } @Override - public ApplicationRelease updatePubAppRelease(String releaseUuid, PublicAppReleaseWrapper publicAppReleaseWrapper, - ApplicationArtifact applicationArtifact) throws ApplicationManagementException { + public ApplicationRelease updatePubAppRelease(String releaseUuid, PublicAppReleaseWrapper publicAppReleaseWrapper) throws ApplicationManagementException { int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true); try { + ApplicationArtifact applicationArtifact = ApplicationManagementUtil.constructApplicationArtifact(publicAppReleaseWrapper.getIconLink(), publicAppReleaseWrapper.getScreenshotLinks(), null, null); ConnectionManagerUtil.beginDBTransaction(); ApplicationDTO applicationDTO = this.applicationDAO.getAppWithRelatedRelease(releaseUuid, tenantId); validateAppReleaseUpdating(publicAppReleaseWrapper, applicationDTO, applicationArtifact, @@ -3273,6 +3464,12 @@ public class ApplicationManagerImpl implements ApplicationManager { + "release UUID:" + releaseUuid; log.error(msg, e); throw new ApplicationManagementException(msg, e); + } catch (MalformedURLException e) { + throw new ApplicationManagementException("Malformed downloadable URL received for the Public app " + + "release UUID: " + releaseUuid); + } catch (FileDownloaderServiceException e) { + throw new ApplicationManagementException("Error encountered while downloading artifact for the Public app " + + "release UUID: " + releaseUuid); } finally { ConnectionManagerUtil.closeDBConnection(); } @@ -3903,30 +4100,24 @@ public class ApplicationManagerImpl implements ApplicationManager { public void validateEntAppReleaseCreatingRequest(EntAppReleaseWrapper releaseWrapper, String deviceType) throws RequestValidatingException, ApplicationManagementException { validateReleaseCreatingRequest(releaseWrapper, deviceType); - validateBinaryArtifact(releaseWrapper.getBinaryFile()); - validateImageArtifacts(releaseWrapper.getIcon(), releaseWrapper.getScreenshots()); } @Override public void validateCustomAppReleaseCreatingRequest(CustomAppReleaseWrapper releaseWrapper, String deviceType) throws RequestValidatingException, ApplicationManagementException { validateReleaseCreatingRequest(releaseWrapper, deviceType); - validateBinaryArtifact(releaseWrapper.getBinaryFile()); - validateImageArtifacts(releaseWrapper.getIcon(), releaseWrapper.getScreenshots()); } @Override public void validateWebAppReleaseCreatingRequest(WebAppReleaseWrapper releaseWrapper) throws RequestValidatingException, ApplicationManagementException { validateReleaseCreatingRequest(releaseWrapper, Constants.ANY); - validateImageArtifacts(releaseWrapper.getIcon(), releaseWrapper.getScreenshots()); } @Override public void validatePublicAppReleaseCreatingRequest(PublicAppReleaseWrapper releaseWrapper, String deviceType) throws RequestValidatingException, ApplicationManagementException { validateReleaseCreatingRequest(releaseWrapper, deviceType); - validateImageArtifacts(releaseWrapper.getIcon(), releaseWrapper.getScreenshots()); validatePublicAppReleasePackageName(releaseWrapper.getPackageName()); } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileDownloaderServiceProvider.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileDownloaderServiceProvider.java new file mode 100644 index 0000000000..73facdc367 --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileDownloaderServiceProvider.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.core.impl; + +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileMetaEntry; +import io.entgra.device.mgt.core.application.mgt.common.TransferLink; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileDownloaderServiceException; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileTransferServiceException; +import io.entgra.device.mgt.core.application.mgt.common.services.FileDownloaderService; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; +import io.entgra.device.mgt.core.application.mgt.core.internal.DataHolder; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class FileDownloaderServiceProvider { + private static final Log log = LogFactory.getLog(FileDownloaderServiceProvider.class); + private static final FileTransferService fileTransferService = DataHolder.getInstance().getFileTransferService(); + private static final LocalFileDownloaderService localFileDownloaderService = new LocalFileDownloaderService(); + private static final RemoteFileDownloaderService remoteFileDownloaderService = new RemoteFileDownloaderService(); + public static FileDownloaderService getFileDownloaderService(URL downloadUrl) throws FileDownloaderServiceException { + try { + if (fileTransferService.isExistsOnLocal(downloadUrl)) { + return localFileDownloaderService; + } + return remoteFileDownloaderService; + } catch (FileTransferServiceException e) { + String msg = "Error encountered while acquiring file downloader service"; + log.error(msg, e); + throw new FileDownloaderServiceException(msg, e); + } + } + + /** + * Class holing the implementation of the local file downloading service + */ + private static class LocalFileDownloaderService implements FileDownloaderService { + @Override + public FileDescriptor download(URL downloadUrl) throws FileDownloaderServiceException { + try { + return fileTransferService.resolve(downloadUrl); + } catch (FileTransferServiceException e) { + String msg = "Error encountered while downloading file pointing by " + downloadUrl; + log.error(msg, e); + throw new FileDownloaderServiceException(msg, e); + } + } + } + + /** + * Class holing the implementation of the remote file downloading service + */ + private static class RemoteFileDownloaderService implements FileDownloaderService { + private static final OkHttpClient okhttpClient = + new OkHttpClient.Builder().connectTimeout(500, TimeUnit.MILLISECONDS).build(); + @Override + public FileDescriptor download(URL downloadUrl) throws FileDownloaderServiceException { + FileMetaEntry fileMetaEntry = getFileMetaEntry(downloadUrl); + try { + TransferLink transferLink = fileTransferService.generateUploadLink(fileMetaEntry); + FileDescriptor fileDescriptor = fileTransferService.resolve(new URL(transferLink.getDirectTransferLink() + + "/" + fileMetaEntry.getFileName() + "." + fileMetaEntry.getExtension())); + FileUtils.copyURLToFile(downloadUrl, new File(fileDescriptor.getAbsolutePath()), + 15000, 3600000); + return fileDescriptor; + } catch (FileTransferServiceException | IOException e) { + String msg = "Error encountered while downloading file"; + log.error(msg, e); + throw new FileDownloaderServiceException(msg, e); + } + } + + /** + * Generate the {@link FileMetaEntry} from the remote file + * @param downloadUrl Remote file URL + * @return {@link FileMetaEntry} + * @throws FileDownloaderServiceException Throws when error encountered while generating {@link FileMetaEntry} + */ + private FileMetaEntry getFileMetaEntry(URL downloadUrl) throws FileDownloaderServiceException { + Request request = new Request.Builder().url(downloadUrl).head().build(); + try (Response response = okhttpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new FileDownloaderServiceException("Unexpected response code received for the remote url " + downloadUrl); + } + String contentDisposition = response.header("Content-Disposition"); + String[] fileNameSegments = getFileNameSegments(contentDisposition); + FileMetaEntry fileMetaEntry = new FileMetaEntry(); + fileMetaEntry.setSize(Long.parseLong(Objects.requireNonNull(response.header("Content-Length")))); + fileMetaEntry.setFileName(fileNameSegments[0] + "-" + UUID.randomUUID()); + fileMetaEntry.setExtension(fileNameSegments[1]); + return fileMetaEntry; + } catch (IOException e) { + throw new FileDownloaderServiceException("IO error occurred while constructing file name for the remote url " + downloadUrl); + } + } + + /** + * Extract file name segments(filename & extensions) from content disposition header + * @param contentDisposition Content disposition header value + * @return Array of name segments + * @throws FileDownloaderServiceException Throws when error occurred while extracting name segments + */ + private static String[] getFileNameSegments(String contentDisposition) throws FileDownloaderServiceException { + if (contentDisposition == null) { + throw new FileDownloaderServiceException("Cannot determine the file name for the remote file"); + } + String []contentDispositionSegments = contentDisposition.split("="); + if (contentDispositionSegments.length != 2) { + throw new FileDownloaderServiceException("Error encountered when constructing file name"); + } + String fullQualifiedName = contentDispositionSegments[contentDispositionSegments.length - 1].replace("\"", ""); + String []fileNameSegments = fullQualifiedName.split("\\.(?=[^.]+$)"); + if (fileNameSegments.length != 2) { + throw new FileDownloaderServiceException("Error encountered when constructing file name"); + } + return fileNameSegments; + } + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileTransferServiceImpl.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileTransferServiceImpl.java new file mode 100644 index 0000000000..52789308fd --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/impl/FileTransferServiceImpl.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.core.impl; + +import io.entgra.device.mgt.core.application.mgt.common.ChunkDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileMetaEntry; +import io.entgra.device.mgt.core.application.mgt.common.TransferLink; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileTransferServiceException; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; +import io.entgra.device.mgt.core.application.mgt.core.exception.FileTransferServiceHelperUtilException; +import io.entgra.device.mgt.core.application.mgt.core.util.FileTransferServiceHelperUtil; +import io.entgra.device.mgt.core.device.mgt.common.exceptions.NotFoundException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Path; + +public class FileTransferServiceImpl implements FileTransferService { + private final static Log log = LogFactory.getLog(FileTransferServiceImpl.class); + private static volatile FileTransferServiceImpl INSTANCE; + + private FileTransferServiceImpl() throws FileTransferServiceException { + try { + FileTransferServiceHelperUtil.createDefaultRootStructure(); + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Error occurred while initializing file transfer service"; + log.error(msg, e); + throw new FileTransferServiceException(msg, e); + } + } + + public static FileTransferService getInstance() throws FileTransferServiceException{ + if (INSTANCE == null) { + synchronized (FileTransferServiceImpl.class) { + if (INSTANCE == null) { + INSTANCE = new FileTransferServiceImpl(); + } + } + } + return INSTANCE; + } + + @Override + public TransferLink generateUploadLink(FileMetaEntry fileMetaEntry) throws FileTransferServiceException { + try { + Path artifactHolder = FileTransferServiceHelperUtil.createNewArtifactHolder(fileMetaEntry); + String []pathSegments = artifactHolder.toString().split(FileSystems.getDefault().getSeparator()); + TransferLink.TransferLinkBuilder transferLinkBuilder = + new TransferLink.TransferLinkBuilder(pathSegments[pathSegments.length - 1]); + return transferLinkBuilder.build(); + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Error encountered while generating upload link"; + log.error(msg, e); + throw new FileTransferServiceException(msg, e); + } + } + + @Override + public ChunkDescriptor resolve(String artifactHolder, InputStream chunk) throws FileTransferServiceException, NotFoundException { + ChunkDescriptor chunkDescriptor = new ChunkDescriptor(); + try { + FileTransferServiceHelperUtil.populateChunkDescriptor(artifactHolder, chunk, chunkDescriptor); + return chunkDescriptor; + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Error occurred while resolving chuck descriptor for " + artifactHolder; + log.error(msg); + throw new FileTransferServiceException(msg, e); + } + } + + @Override + public void writeChunk(ChunkDescriptor chunkDescriptor) throws FileTransferServiceException { + try { + FileTransferServiceHelperUtil.writeChunk(chunkDescriptor); + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Failed to write data to artifact located in " + chunkDescriptor.getAssociateFileDescriptor().getAbsolutePath(); + log.error(msg); + throw new FileTransferServiceException(msg, e); + } + } + + @Override + public boolean isExistsOnLocal(URL downloadUrl) throws FileTransferServiceException { + try { + return FileTransferServiceHelperUtil.resolve(downloadUrl) != null; + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Error occurred while checking the existence of artifact on the local environment"; + log.error(msg, e); + throw new FileTransferServiceException(msg, e); + } + } + + @Override + public FileDescriptor resolve(URL downloadUrl) throws FileTransferServiceException { + try { + return FileTransferServiceHelperUtil.resolve(downloadUrl); + } catch (FileTransferServiceHelperUtilException e) { + String msg = "Error occurred while resolving file descriptor pointing from " + downloadUrl; + log.error(msg, e); + throw new FileTransferServiceException(msg, e); + } + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java index 267105e5f7..20cd9febbc 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/ApplicationManagementServiceComponent.java @@ -21,6 +21,7 @@ import io.entgra.device.mgt.core.application.mgt.common.config.LifecycleState; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationStorageManager; import io.entgra.device.mgt.core.application.mgt.common.services.AppmDataHandler; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; import io.entgra.device.mgt.core.application.mgt.common.services.ReviewManager; import io.entgra.device.mgt.core.application.mgt.common.services.SPApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.SubscriptionManager; @@ -28,6 +29,7 @@ import io.entgra.device.mgt.core.application.mgt.common.services.VPPApplicationM import io.entgra.device.mgt.core.application.mgt.core.config.ConfigurationManager; import io.entgra.device.mgt.core.application.mgt.core.dao.common.ApplicationManagementDAOFactory; import io.entgra.device.mgt.core.application.mgt.core.impl.AppmDataHandlerImpl; +import io.entgra.device.mgt.core.application.mgt.core.impl.FileTransferServiceImpl; import io.entgra.device.mgt.core.application.mgt.core.lifecycle.LifecycleStateManager; import io.entgra.device.mgt.core.application.mgt.core.task.ScheduledAppSubscriptionTaskManager; import io.entgra.device.mgt.core.application.mgt.core.util.ApplicationManagementUtil; @@ -123,6 +125,10 @@ public class ApplicationManagementServiceComponent { DataHolder.getInstance().setVppApplicationManager(vppApplicationManager); bundleContext.registerService(VPPApplicationManager.class.getName(), vppApplicationManager, null); + FileTransferService fileTransferService = FileTransferServiceImpl.getInstance(); + DataHolder.getInstance().setFileTransferService(fileTransferService); + bundleContext.registerService(FileTransferService.class.getName(), fileTransferService, null); + ScheduledAppSubscriptionTaskManager taskManager = new ScheduledAppSubscriptionTaskManager(); taskManager.scheduleCleanupTask(); diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java index b8905829ad..80416dcd59 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/internal/DataHolder.java @@ -20,6 +20,7 @@ package io.entgra.device.mgt.core.application.mgt.core.internal; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationStorageManager; import io.entgra.device.mgt.core.application.mgt.common.services.AppmDataHandler; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; import io.entgra.device.mgt.core.application.mgt.common.services.SPApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.ReviewManager; import io.entgra.device.mgt.core.application.mgt.common.services.SubscriptionManager; @@ -55,6 +56,7 @@ public class DataHolder { private AppmDataHandler configManager; private TaskService taskService; + private FileTransferService fileTransferService; private static final DataHolder applicationMgtDataHolder = new DataHolder(); @@ -153,4 +155,12 @@ public class DataHolder { public void setVppApplicationManager(VPPApplicationManager vppApplicationManager) { this.vppApplicationManager = vppApplicationManager; } + + public FileTransferService getFileTransferService() { + return fileTransferService; + } + + public void setFileTransferService(FileTransferService fileTransferService) { + this.fileTransferService = fileTransferService; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/APIUtil.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/APIUtil.java index 18d52fc624..789ac3de0a 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/APIUtil.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/APIUtil.java @@ -74,6 +74,7 @@ public class APIUtil { private static volatile AppmDataHandler appmDataHandler; private static volatile VPPApplicationManager vppApplicationManager; private static volatile MetadataManagementService metadataManagementService; + private static volatile FileTransferService fileTransferService; public static SPApplicationManager getSPApplicationManager() { if (SPApplicationManager == null) { @@ -591,4 +592,16 @@ public class APIUtil { } return metadataManagementService; } + + public static FileTransferService getFileTransferService() { + if (fileTransferService == null) { + synchronized (APIUtil.class) { + if (fileTransferService == null) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + fileTransferService = (FileTransferService) ctx.getOSGiService(FileTransferService.class, null); + } + } + } + return fileTransferService; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/ApplicationManagementUtil.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/ApplicationManagementUtil.java index e8bd05c81a..b3b2d65f0a 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/ApplicationManagementUtil.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/ApplicationManagementUtil.java @@ -19,16 +19,20 @@ package io.entgra.device.mgt.core.application.mgt.core.util; import io.entgra.device.mgt.core.application.mgt.common.ApplicationArtifact; import io.entgra.device.mgt.core.application.mgt.common.FileDataHolder; +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; import io.entgra.device.mgt.core.application.mgt.common.LifecycleChanger; import io.entgra.device.mgt.core.application.mgt.common.dto.ApplicationDTO; import io.entgra.device.mgt.core.application.mgt.common.dto.ItuneAppDTO; import io.entgra.device.mgt.core.application.mgt.common.exception.ApplicationManagementException; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileDownloaderServiceException; +import io.entgra.device.mgt.core.application.mgt.common.exception.FileTransferServiceException; import io.entgra.device.mgt.core.application.mgt.common.exception.InvalidConfigurationException; import io.entgra.device.mgt.core.application.mgt.common.exception.RequestValidatingException; import io.entgra.device.mgt.core.application.mgt.common.response.Application; import io.entgra.device.mgt.core.application.mgt.common.response.Category; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.ApplicationStorageManager; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; import io.entgra.device.mgt.core.application.mgt.common.services.ReviewManager; import io.entgra.device.mgt.core.application.mgt.common.services.SPApplicationManager; import io.entgra.device.mgt.core.application.mgt.common.services.SubscriptionManager; @@ -45,10 +49,12 @@ import io.entgra.device.mgt.core.application.mgt.common.wrapper.WebAppWrapper; import io.entgra.device.mgt.core.application.mgt.core.config.ConfigurationManager; import io.entgra.device.mgt.core.application.mgt.core.config.Extension; import io.entgra.device.mgt.core.application.mgt.core.exception.BadRequestException; +import io.entgra.device.mgt.core.application.mgt.core.impl.FileDownloaderServiceProvider; import io.entgra.device.mgt.core.application.mgt.core.impl.VppApplicationManagerImpl; import io.entgra.device.mgt.core.application.mgt.core.lifecycle.LifecycleStateManager; import io.entgra.device.mgt.core.device.mgt.common.Base64File; import io.entgra.device.mgt.core.device.mgt.common.DeviceManagementConstants; +import io.entgra.device.mgt.core.device.mgt.common.app.mgt.App; import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.MetadataManagementService; import io.entgra.device.mgt.core.device.mgt.core.common.util.FileUtil; import io.entgra.device.mgt.core.device.mgt.core.metadata.mgt.MetadataManagementServiceImpl; @@ -159,6 +165,49 @@ public class ApplicationManagementUtil { return applicationArtifact; } + public static ApplicationArtifact constructApplicationArtifact(String iconLink, List screenshotLinks, String artifactLink, String bannerLink) + throws MalformedURLException, FileDownloaderServiceException { + ApplicationArtifact applicationArtifact = new ApplicationArtifact(); + FileDescriptor fileDescriptor; + if (artifactLink != null) { + URL artifactLinkUrl = new URL(artifactLink); + fileDescriptor = FileDownloaderServiceProvider.getFileDownloaderService(artifactLinkUrl).download(artifactLinkUrl); + applicationArtifact.setInstallerName(fileDescriptor.getFullQualifiedName()); + applicationArtifact.setInstallerStream(fileDescriptor.getFile()); + } + + if (iconLink != null) { + URL iconLinkUrl = new URL(iconLink); + fileDescriptor = FileDownloaderServiceProvider.getFileDownloaderService(iconLinkUrl).download(iconLinkUrl); + applicationArtifact.setIconName(fileDescriptor.getFullQualifiedName()); + applicationArtifact.setIconStream(fileDescriptor.getFile()); + } + + if (bannerLink != null) { + URL bannerLinkUrl = new URL(bannerLink); + fileDescriptor = FileDownloaderServiceProvider.getFileDownloaderService(bannerLinkUrl).download(bannerLinkUrl); + applicationArtifact.setBannerName(fileDescriptor.getFullQualifiedName()); + applicationArtifact.setBannerStream(fileDescriptor.getFile()); + } + + if (screenshotLinks != null) { + Map screenshotData = new TreeMap<>(); + // This is to handle cases in which multiple screenshots have the same name + Map screenshotNameCount = new HashMap<>(); + URL screenshotLinkUrl; + for (String screenshotLink : screenshotLinks) { + screenshotLinkUrl = new URL(screenshotLink); + fileDescriptor = FileDownloaderServiceProvider.getFileDownloaderService(screenshotLinkUrl).download(screenshotLinkUrl); + String screenshotName = fileDescriptor.getFullQualifiedName(); + screenshotNameCount.put(screenshotName, screenshotNameCount.getOrDefault(screenshotName, 0) + 1); + screenshotName = FileUtil.generateDuplicateFileName(screenshotName, screenshotNameCount.get(screenshotName)); + screenshotData.put(screenshotName, fileDescriptor.getFile()); + } + applicationArtifact.setScreenshots(screenshotData); + } + return applicationArtifact; + } + /** * * @param base64File Base64File that should be converted to FileDataHolder bean @@ -251,6 +300,41 @@ public class ApplicationManagementUtil { throw new IllegalArgumentException("Provided bean does not belong to an Application Wrapper"); } + public static boolean getRemoteStatus(T appWrapper) { + if (!isReleaseAvailable(appWrapper)) { + return false; + } + if (appWrapper instanceof ApplicationWrapper) { + return getRemoteStatusFromWrapper(((ApplicationWrapper) appWrapper).getEntAppReleaseWrappers().get(0)); + } + if (appWrapper instanceof PublicAppWrapper) { + return getRemoteStatusFromWrapper(((PublicAppWrapper) appWrapper).getPublicAppReleaseWrappers().get(0)); + } + if (appWrapper instanceof WebAppWrapper) { + return getRemoteStatusFromWrapper(((WebAppWrapper) appWrapper).getWebAppReleaseWrappers().get(0)); + } + if (appWrapper instanceof CustomAppWrapper) { + return getRemoteStatusFromWrapper(((CustomAppWrapper) appWrapper).getCustomAppReleaseWrappers().get(0)); + } + throw new IllegalArgumentException("Provided bean does not belong to an Application Wrapper"); + } + + public static boolean getRemoteStatusFromWrapper(T releaseWrapper) { + if (releaseWrapper instanceof EntAppReleaseWrapper) { + return ((EntAppReleaseWrapper) releaseWrapper).isRemoteStatus(); + } + if (releaseWrapper instanceof PublicAppReleaseWrapper) { + return ((PublicAppReleaseWrapper) releaseWrapper).isRemoteStatus(); + } + if (releaseWrapper instanceof WebAppReleaseWrapper) { + return ((WebAppReleaseWrapper) releaseWrapper).isRemoteStatus(); + } + if (releaseWrapper instanceof CustomAppReleaseWrapper) { + return ((CustomAppReleaseWrapper) releaseWrapper).isRemoteStatus(); + } + throw new IllegalArgumentException("Provided bean does not belong to an Release Wrapper"); + } + public static T getInstance(Extension extension, Class cls) throws InvalidConfigurationException { try { Class theClass = Class.forName(extension.getClassName()); @@ -279,13 +363,12 @@ public class ApplicationManagementUtil { ApplicationManager applicationManager = APIUtil.getApplicationManager(); List categories = applicationManager.getRegisteredCategories(); if (product != null && product.getVersion() != null) { - // Generate artifacts - ApplicationArtifact applicationArtifact = generateArtifacts(product); List packageNamesOfApps = new ArrayList<>(); packageNamesOfApps.add(product.getPackageName()); List existingApps = applicationManager.getApplications(packageNamesOfApps); + PublicAppReleaseWrapper publicAppReleaseWrapper = generatePublicAppReleaseWrapper(product); if (existingApps != null && existingApps.size() > 0) { Application app = existingApps.get(0); @@ -293,7 +376,6 @@ public class ApplicationManagementUtil { ApplicationUpdateWrapper applicationUpdateWrapper = generatePubAppUpdateWrapper(product, categories); applicationManager.updateApplication(app.getId(), applicationUpdateWrapper); - PublicAppReleaseWrapper publicAppReleaseWrapper = new PublicAppReleaseWrapper(); if (app.getSubMethod() .equalsIgnoreCase(Constants.ApplicationProperties.FREE_SUB_METHOD)) { publicAppReleaseWrapper.setPrice(0.0); @@ -306,56 +388,48 @@ public class ApplicationManagementUtil { publicAppReleaseWrapper.setVersion(product.getVersion()); publicAppReleaseWrapper.setSupportedOsVersions("4.0-12.3"); applicationManager.updatePubAppRelease(app.getApplicationReleases().get(0).getUuid(), - publicAppReleaseWrapper, applicationArtifact); + publicAppReleaseWrapper); return; } } else { // Generate App wrapper PublicAppWrapper publicAppWrapper = generatePubAppWrapper(product, categories); - PublicAppReleaseWrapper appReleaseWrapper = new PublicAppReleaseWrapper(); - - if (publicAppWrapper.getSubMethod() - .equalsIgnoreCase(Constants.ApplicationProperties.FREE_SUB_METHOD)) { - appReleaseWrapper.setPrice(0.0); - } else { - appReleaseWrapper.setPrice(1.0); - } - - appReleaseWrapper.setDescription(product.getDescription()); - appReleaseWrapper.setReleaseType("ga"); - appReleaseWrapper.setVersion(product.getVersion()); - appReleaseWrapper.setPackageName(product.getPackageName()); - appReleaseWrapper.setSupportedOsVersions("4.0-12.3"); publicAppWrapper.setPublicAppReleaseWrappers( - Arrays.asList(new PublicAppReleaseWrapper[]{appReleaseWrapper})); - - try { - updateImages(appReleaseWrapper, applicationArtifact.getIconName(), - applicationArtifact.getIconStream(), applicationArtifact.getScreenshots()); - - Application application = applicationManager.createApplication(publicAppWrapper, false); - if (application != null && (application.getApplicationReleases().get(0).getCurrentStatus() == null - || application.getApplicationReleases().get(0).getCurrentStatus().equals("CREATED"))) { - String uuid = application.getApplicationReleases().get(0).getUuid(); - LifecycleChanger lifecycleChanger = new LifecycleChanger(); - lifecycleChanger.setAction("IN-REVIEW"); - applicationManager.changeLifecycleState(uuid, lifecycleChanger); - lifecycleChanger.setAction("APPROVED"); - applicationManager.changeLifecycleState(uuid, lifecycleChanger); - lifecycleChanger.setAction("PUBLISHED"); - applicationManager.changeLifecycleState(uuid, lifecycleChanger); - } - } catch (IOException e) { - String msg = "Error while downloading images of release."; - log.error(msg); - throw new ApplicationManagementException(msg, e); + Arrays.asList(new PublicAppReleaseWrapper[]{publicAppReleaseWrapper})); + + Application application = applicationManager.createApplication(publicAppWrapper, false); + if (application != null && (application.getApplicationReleases().get(0).getCurrentStatus() == null + || application.getApplicationReleases().get(0).getCurrentStatus().equals("CREATED"))) { + String uuid = application.getApplicationReleases().get(0).getUuid(); + LifecycleChanger lifecycleChanger = new LifecycleChanger(); + lifecycleChanger.setAction("IN-REVIEW"); + applicationManager.changeLifecycleState(uuid, lifecycleChanger); + lifecycleChanger.setAction("APPROVED"); + applicationManager.changeLifecycleState(uuid, lifecycleChanger); + lifecycleChanger.setAction("PUBLISHED"); + applicationManager.changeLifecycleState(uuid, lifecycleChanger); } } } } + private static PublicAppReleaseWrapper generatePublicAppReleaseWrapper(ItuneAppDTO product) { + PublicAppReleaseWrapper publicAppReleaseWrapper = new PublicAppReleaseWrapper(); + publicAppReleaseWrapper.setDescription(product.getDescription()); + publicAppReleaseWrapper.setReleaseType("ga"); + publicAppReleaseWrapper.setVersion(product.getVersion()); + publicAppReleaseWrapper.setPackageName(product.getPackageName()); + publicAppReleaseWrapper.setSupportedOsVersions("4.0-12.3"); + publicAppReleaseWrapper.setIconLink(product.getIconURL()); + publicAppReleaseWrapper.setRemoteStatus(false); + List screenshotUrls = new ArrayList<>(Collections.nCopies(3, product.getIconURL())); + publicAppReleaseWrapper.setScreenshotLinks(screenshotUrls); + publicAppReleaseWrapper.setPrice(1.0); + return publicAppReleaseWrapper; + } + private static PublicAppWrapper generatePubAppWrapper(ItuneAppDTO product, List categories) { PublicAppWrapper publicAppWrapper = new PublicAppWrapper(); publicAppWrapper.setName(product.getTitle()); @@ -560,4 +634,19 @@ public class ApplicationManagementUtil { return sanitizedName; } } + + public static List deriveApplicationWithoutRelease(T app) { + List releaseWrappers = null; + if (app instanceof ApplicationWrapper) { + ApplicationWrapper applicationWrapper = (ApplicationWrapper) app; + releaseWrappers = applicationWrapper.getEntAppReleaseWrappers(); + applicationWrapper.setEntAppReleaseWrappers(Collections.emptyList()); + } + if (app instanceof CustomAppWrapper) { + CustomAppWrapper applicationWrapper = (CustomAppWrapper) app; + releaseWrappers = applicationWrapper.getCustomAppReleaseWrappers(); + applicationWrapper.setCustomAppReleaseWrappers(Collections.emptyList()); + } + return releaseWrappers; + } } diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/FileTransferServiceHelperUtil.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/FileTransferServiceHelperUtil.java new file mode 100644 index 0000000000..e7ce574c4c --- /dev/null +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/main/java/io/entgra/device/mgt/core/application/mgt/core/util/FileTransferServiceHelperUtil.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package io.entgra.device.mgt.core.application.mgt.core.util; + +import com.google.gson.Gson; +import io.entgra.device.mgt.core.application.mgt.common.ChunkDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileMetaEntry; +import io.entgra.device.mgt.core.application.mgt.core.exception.FileTransferServiceHelperUtilException; +import io.entgra.device.mgt.core.device.mgt.common.exceptions.NotFoundException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Objects; +import java.util.UUID; + +public class FileTransferServiceHelperUtil { + private static final Log log = LogFactory.getLog(FileTransferServiceHelperUtil.class); + private static final String ROOT = "iot-artifact-holder"; + private static final String SYSTEM_PROPERTY_TEMP_DIR = "java.io.tmpdir"; + private static final String META_ENTRY_FILE_NAME = ".meta.json"; + private static final Gson gson = new Gson(); + public static void createDefaultRootStructure() throws FileTransferServiceHelperUtilException { + try { + Path root = Paths.get(System.getProperty(SYSTEM_PROPERTY_TEMP_DIR), ROOT); + if (Files.notExists(root)) { + setMinimumPermissions(Files.createDirectory(root)); + } + + if (!Files.isDirectory(root)) { + throw new FileTransferServiceHelperUtilException(root.toAbsolutePath() + " is not a directory"); + } + setMinimumPermissions(root); + } catch (IOException e) { + String msg = "Error encountered while creating default artifact root structure"; + log.error(msg, e); + throw new FileTransferServiceHelperUtilException(msg, e); + } + } + + public static Path createNewArtifactHolder(FileMetaEntry fileMetaEntry) throws FileTransferServiceHelperUtilException { + try { + Path artifactHolder = Paths.get(System.getProperty(SYSTEM_PROPERTY_TEMP_DIR), ROOT, UUID.randomUUID().toString()); + if (Files.exists(artifactHolder)) { + throw new FileTransferServiceHelperUtilException("Artifact holder already exists in " + artifactHolder); + } + setMinimumPermissions(Files.createDirectory(artifactHolder)); + createMetaEntry(fileMetaEntry, artifactHolder); + createArtifactFile(fileMetaEntry, artifactHolder); + return artifactHolder; + } catch (IOException e) { + String msg = "Error occurred while creating artifact holder"; + log.error(msg, e); + throw new FileTransferServiceHelperUtilException(msg, e); + } + } + + public static void populateChunkDescriptor(String artifactHolder, InputStream chunk, ChunkDescriptor chunkDescriptor) + throws FileTransferServiceHelperUtilException, NotFoundException { + Path holder = locateArtifactHolder(artifactHolder); + Path metaEntry = locateMetaEntry(holder); + chunkDescriptor.setChunk(chunk); + FileDescriptor fileDescriptor = new FileDescriptor(); + populateFileDescriptor(metaEntry, holder, fileDescriptor); + chunkDescriptor.setAssociateFileDescriptor(fileDescriptor); + } + + public static void populateFileDescriptor(String artifactHolder, FileDescriptor fileDescriptor) + throws FileTransferServiceHelperUtilException, NotFoundException { + Path holder = locateArtifactHolder(artifactHolder); + Path metaEntry = locateMetaEntry(holder); + populateFileDescriptor(metaEntry, holder, fileDescriptor); + } + + public static void populateFileDescriptor(Path metaEntry, Path artifactHolder, FileDescriptor fileDescriptor) throws FileTransferServiceHelperUtilException { + try { + byte []metaEntryByteContent = Files.readAllBytes(metaEntry); + FileMetaEntry fileMetaEntry = gson.fromJson(new String(metaEntryByteContent, StandardCharsets.UTF_8), FileMetaEntry.class); + fileDescriptor.setFileName(fileMetaEntry.getFileName()); + fileDescriptor.setActualFileSize(fileMetaEntry.getSize()); + fileDescriptor.setFullQualifiedName(fileMetaEntry.getFileName() + "." + fileMetaEntry.getExtension()); + Path artifact = artifactHolder.resolve(fileDescriptor.getFullQualifiedName()); + fileDescriptor.setAbsolutePath(artifact.toAbsolutePath().toString()); + fileDescriptor.setExtension(fileMetaEntry.getExtension()); + fileDescriptor.setFile(Files.newInputStream(artifact)); + } catch (IOException e) { + String msg = "Error encountered while populating chuck descriptor"; + log.error(msg, e); + throw new FileTransferServiceHelperUtilException(msg, e); + } + } + + private static Path locateArtifactHolder(String artifactHolder) throws FileTransferServiceHelperUtilException, NotFoundException { + Path holder = Paths.get(System.getProperty(SYSTEM_PROPERTY_TEMP_DIR), ROOT, artifactHolder); + if (Files.notExists(holder)) { + throw new NotFoundException(holder.toAbsolutePath() + " is not exists"); + } + + if (!Files.isDirectory(holder)) { + throw new FileTransferServiceHelperUtilException(holder.toFile().getAbsolutePath() + " is not a directory"); + } + return holder; + } + + private static Path locateMetaEntry(Path artifactHolder) throws FileTransferServiceHelperUtilException { + Path metaEntry = artifactHolder.resolve(META_ENTRY_FILE_NAME); + if (Files.notExists(metaEntry) || Files.isDirectory(metaEntry)) { + throw new FileTransferServiceHelperUtilException("Can't locate " + META_ENTRY_FILE_NAME); + } + + if (!Files.isReadable(metaEntry)) { + throw new FileTransferServiceHelperUtilException("Unreadable " + META_ENTRY_FILE_NAME); + } + return metaEntry; + } + + public static void writeChunk(ChunkDescriptor chunkDescriptor) throws FileTransferServiceHelperUtilException { + if (chunkDescriptor == null) { + throw new FileTransferServiceHelperUtilException("Received null for chuck descriptor"); + } + FileDescriptor fileDescriptor = chunkDescriptor.getAssociateFileDescriptor(); + if (fileDescriptor == null) { + throw new FileTransferServiceHelperUtilException("Target file descriptor is missing for retrieved chunk"); + } + Path artifact = Paths.get(fileDescriptor.getAbsolutePath()); + try { + InputStream chuckStream = chunkDescriptor.getChunk(); + byte []chunk = new byte[chuckStream.available()]; + chuckStream.read(chunk); + Files.write(artifact, chunk, StandardOpenOption.CREATE, StandardOpenOption.SYNC, StandardOpenOption.APPEND); + } catch (IOException e) { + String msg = "Error encountered while writing to the " + artifact; + log.error(msg, e); + throw new FileTransferServiceHelperUtilException(msg, e); + } + } + + public static FileDescriptor resolve(URL downloadUrl) throws FileTransferServiceHelperUtilException { + if (downloadUrl == null) { + throw new FileTransferServiceHelperUtilException("Received null for download url"); + } + + if (!Objects.equals(System.getProperty("iot.gateway.host"), downloadUrl.getHost())) { + if (log.isDebugEnabled()) { + log.debug("Host not match with " + System.getProperty("iot.gateway.host")); + } + return null; + } + + String []urlPathSegments = downloadUrl.getPath().split("/"); + if (urlPathSegments.length < 2) { + if (log.isDebugEnabled()) { + log.debug("URL patch segments contain less than 2 segments"); + } + return null; + } + + String file = urlPathSegments[urlPathSegments.length - 1]; + String artifactHolder = urlPathSegments[urlPathSegments.length - 2]; + try { + FileDescriptor fileDescriptor = new FileDescriptor(); + populateFileDescriptor(artifactHolder, fileDescriptor); + if (!Objects.equals(file, fileDescriptor.getFullQualifiedName())) { + if (log.isDebugEnabled()) { + log.debug("File name not equal to the file exists in the local"); + } + return null; + } + return fileDescriptor; + } catch (NotFoundException e) { + if (log.isDebugEnabled()) { + log.debug("Local URL not found in the system"); + } + return null; + } + } + + private static void setMinimumPermissions(Path path) throws FileTransferServiceHelperUtilException { + File file = path.toFile(); + if (!file.setReadable(true, true)) { + throw new FileTransferServiceHelperUtilException("Failed to set read permission for " + file.getAbsolutePath()); + } + + if (!file.setWritable(true, true)) { + throw new FileTransferServiceHelperUtilException("Failed to set write permission for " + file.getAbsolutePath()); + } + } + + private static void createMetaEntry(FileMetaEntry fileMetaEntry, Path artifactHolder) throws FileTransferServiceHelperUtilException { + try { + Path metaEntry = artifactHolder.resolve(META_ENTRY_FILE_NAME); + String fileMetaJsonContent = gson.toJson(fileMetaEntry); + Files.write(metaEntry, fileMetaJsonContent.getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.SYNC); + } catch (IOException e) { + throw new FileTransferServiceHelperUtilException("Error encountered while creating meta entry", e); + } + } + + private static void createArtifactFile(FileMetaEntry fileMetaEntry, Path artifactHolder) throws FileTransferServiceHelperUtilException { + try { + Path artifactFile = artifactHolder.resolve(fileMetaEntry.getFileName() + "." + fileMetaEntry.getExtension()); + fileMetaEntry.setAbsolutePath(artifactFile.toAbsolutePath().toString()); + Files.createFile(artifactFile); + setMinimumPermissions(artifactFile); + } catch (IOException e) { + throw new FileTransferServiceHelperUtilException("Error encountered while creating artifact file", e); + } + } +} diff --git a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/test/java/io/entgra/device/mgt/core/application/mgt/core/management/ApplicationManagementTest.java b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/test/java/io/entgra/device/mgt/core/application/mgt/core/management/ApplicationManagementTest.java index 9eac36785e..e78b6196d0 100644 --- a/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/test/java/io/entgra/device/mgt/core/application/mgt/core/management/ApplicationManagementTest.java +++ b/components/application-mgt/io.entgra.device.mgt.core.application.mgt.core/src/test/java/io/entgra/device/mgt/core/application/mgt/core/management/ApplicationManagementTest.java @@ -19,8 +19,13 @@ package io.entgra.device.mgt.core.application.mgt.core.management; import io.entgra.device.mgt.core.application.mgt.common.ApplicationArtifact; import io.entgra.device.mgt.core.application.mgt.common.ApplicationList; +import io.entgra.device.mgt.core.application.mgt.common.ChunkDescriptor; +import io.entgra.device.mgt.core.application.mgt.common.FileMetaEntry; import io.entgra.device.mgt.core.application.mgt.common.Filter; import io.entgra.device.mgt.core.application.mgt.common.LifecycleState; +import io.entgra.device.mgt.core.application.mgt.common.TransferLink; +import io.entgra.device.mgt.core.application.mgt.common.services.FileTransferService; +import io.entgra.device.mgt.core.application.mgt.core.impl.FileTransferServiceImpl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.cxf.jaxrs.ext.multipart.Attachment; @@ -52,6 +57,10 @@ import io.entgra.device.mgt.core.device.mgt.core.dto.DeviceTypeVersion; import io.entgra.device.mgt.core.device.mgt.core.service.DeviceManagementProviderServiceImpl; import java.io.File; +import java.io.FileInputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -104,26 +113,66 @@ public class ApplicationManagementTest extends BaseTestCase { releaseWrapper.setSupportedOsVersions("4.0-7.0"); - File banner = new File("src/test/resources/samples/app1/banner1.jpg"); - File icon = new File("src/test/resources/samples/app1/icon.png"); - File ss1 = new File("src/test/resources/samples/app1/shot1.png"); - File ss2 = new File("src/test/resources/samples/app1/shot2.png"); - File ss3 = new File("src/test/resources/samples/app1/shot3.png"); - - Base64File bannerBase64 = new Base64File("banner", FileUtil.fileToBase64String(banner)); - Base64File iconBase64 = new Base64File("icon", FileUtil.fileToBase64String(icon)); - Base64File ss1Base64 = new Base64File("ss1", FileUtil.fileToBase64String(ss1)); - Base64File ss2Base64 = new Base64File("ss2", FileUtil.fileToBase64String(ss2)); - Base64File ss3Base64 = new Base64File("ss3", FileUtil.fileToBase64String(ss3)); - - File apk = new File("src/test/resources/samples/app1/sample.apk"); - Base64File apkBase64 = new Base64File("apk", FileUtil.fileToBase64String(apk)); - - - releaseWrapper.setBanner(bannerBase64); - releaseWrapper.setIcon(iconBase64); - releaseWrapper.setBinaryFile(apkBase64); - releaseWrapper.setScreenshots(Arrays.asList(ss1Base64, ss2Base64, ss3Base64)); + FileTransferService fileTransferService = FileTransferServiceImpl.getInstance(); + DataHolder.getInstance().setFileTransferService(fileTransferService); + + FileMetaEntry metaEntry = new FileMetaEntry(); + TransferLink transferLink; + String []segments; + ChunkDescriptor chunkDescriptor; + + metaEntry.setFileName("banner1"); + metaEntry.setExtension("jpg"); + metaEntry.setSize(179761); + transferLink = fileTransferService.generateUploadLink(metaEntry); + segments = transferLink.getRelativeTransferLink().split("/"); + chunkDescriptor = fileTransferService. + resolve(segments[segments.length-1], Files.newInputStream( + Paths.get("src/test/resources/samples/app1/banner1.jpg"))); + fileTransferService.writeChunk(chunkDescriptor); + releaseWrapper.setBannerLink(transferLink.getDirectTransferLink() + "/banner1.jpg"); + + metaEntry.setFileName("icon"); + metaEntry.setExtension("png"); + metaEntry.setSize(41236); + transferLink = fileTransferService.generateUploadLink(metaEntry); + segments = transferLink.getRelativeTransferLink().split("/"); + chunkDescriptor = fileTransferService. + resolve(segments[segments.length-1], Files.newInputStream( + Paths.get("src/test/resources/samples/app1/icon.png"))); + fileTransferService.writeChunk(chunkDescriptor); + releaseWrapper.setIconLink(transferLink.getDirectTransferLink() + "/icon.png"); + + List screenshotPaths = Arrays.asList("src/test/resources/samples/app1/shot1.png", + "src/test/resources/samples/app1/shot2.png", "src/test/resources/samples/app1/shot3.png"); + List screenshotLinks = new ArrayList<>(); + String []pathSegments; + for (String path: screenshotPaths) { + pathSegments = path.split("/"); + String fullQualifiedName = pathSegments[pathSegments.length - 1]; + String []nameSegments = fullQualifiedName.split("\\.(?=[^.]+$)"); + metaEntry.setFileName(nameSegments[0]); + metaEntry.setExtension(nameSegments[1]); + metaEntry.setSize(41236); + transferLink = fileTransferService.generateUploadLink(metaEntry); + segments = transferLink.getRelativeTransferLink().split("/"); + chunkDescriptor = fileTransferService. + resolve(segments[segments.length-1], Files.newInputStream(Paths.get(path))); + fileTransferService.writeChunk(chunkDescriptor); + screenshotLinks.add(transferLink.getDirectTransferLink() + "/" + fullQualifiedName); + } + releaseWrapper.setScreenshotLinks(screenshotLinks); + + metaEntry.setFileName("sample"); + metaEntry.setExtension("apk"); + metaEntry.setSize(6259412); + TransferLink apkTransferLink = fileTransferService.generateUploadLink(metaEntry); + segments = apkTransferLink.getRelativeTransferLink().split("/"); + chunkDescriptor = fileTransferService. + resolve(segments[segments.length-1], Files.newInputStream(Paths.get("src/test/resources/samples/app1/sample.apk"))); + fileTransferService.writeChunk(chunkDescriptor); + releaseWrapper.setArtifactLink(apkTransferLink.getDirectTransferLink() + "/sample.apk"); + releaseWrapper.setRemoteStatus(false); entAppReleaseWrappers.add(releaseWrapper); applicationWrapper.setEntAppReleaseWrappers(entAppReleaseWrappers); diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml index 4ce8f1b15e..7ddc51b95e 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml @@ -419,6 +419,7 @@ dm:admin:cea:update dm:admin:cea:delete dm:admin:cea:sync + am:pub:app:upload device-mgt diff --git a/pom.xml b/pom.xml index 2baea92b94..f40557c8f2 100644 --- a/pom.xml +++ b/pom.xml @@ -2178,7 +2178,7 @@ 2.8.5 31.0.1-jre 4.6.0 - 1.13.0 + 2.6.0 9.3.1 1.1.1 1.2