diff --git a/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/GridFsArtifact.java b/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/GridFsArtifact.java index fc3461d9a..ba17be936 100644 --- a/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/GridFsArtifact.java +++ b/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/GridFsArtifact.java @@ -10,13 +10,14 @@ package org.eclipse.hawkbit.artifact.repository; import java.io.InputStream; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import com.mongodb.gridfs.GridFSDBFile; import com.mongodb.gridfs.GridFSFile; /** - * A wrapper object for the {@link DbArtifact} object which returns the + * A wrapper object for the {@link AbstractDbArtifact} object which returns the * {@link InputStream} directly from {@link GridFSDBFile#getInputStream()} which * retrieves when calling {@link #getFileInputStream()} always a new * {@link InputStream} and not the same. @@ -24,7 +25,7 @@ import com.mongodb.gridfs.GridFSFile; * * */ -public class GridFsArtifact extends DbArtifact { +public class GridFsArtifact extends AbstractDbArtifact { private final GridFSFile dbFile; @@ -32,6 +33,8 @@ public class GridFsArtifact extends DbArtifact { * @param dbFile */ public GridFsArtifact(final GridFSFile dbFile) { + super(dbFile.getId().toString(), new DbArtifactHash(dbFile.getFilename(), dbFile.getMD5()), dbFile.getLength(), + dbFile.getContentType()); this.dbFile = dbFile; } diff --git a/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoDBArtifactStore.java b/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoDBArtifactStore.java index 9631e2ff2..61def91a6 100644 --- a/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoDBArtifactStore.java +++ b/extensions/hawkbit-extension-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoDBArtifactStore.java @@ -22,7 +22,7 @@ import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.stream.Collectors; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; @@ -89,7 +89,7 @@ public class MongoDBArtifactStore implements ArtifactRepository { * @return The DbArtifact object or {@code null} if no file exists. */ @Override - public DbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) { + public AbstractDbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) { GridFSDBFile found = gridFs.findOne(new Query() .addCriteria(Criteria.where(FILENAME).is(sha1Hash).and(TENANT_QUERY).is(sanitizeTenant(tenant)))); @@ -104,13 +104,13 @@ public class MongoDBArtifactStore implements ArtifactRepository { } @Override - public DbArtifact store(final String tenant, final InputStream content, final String filename, + public AbstractDbArtifact store(final String tenant, final InputStream content, final String filename, final String contentType) { return store(tenant, content, filename, contentType, null); } @Override - public DbArtifact store(final String tenant, final InputStream content, final String filename, + public AbstractDbArtifact store(final String tenant, final InputStream content, final String filename, final String contentType, final DbArtifactHash hash) { File tempFile = null; try { @@ -155,8 +155,8 @@ public class MongoDBArtifactStore implements ArtifactRepository { } - private DbArtifact store(final String t, final InputStream content, final String contentType, final OutputStream os, - final File tempFile, final DbArtifactHash hash) { + private AbstractDbArtifact store(final String t, final InputStream content, final String contentType, + final OutputStream os, final File tempFile, final DbArtifactHash hash) { final GridFsArtifact storedArtifact; final String tenant = sanitizeTenant(t); try { @@ -216,15 +216,16 @@ public class MongoDBArtifactStore implements ArtifactRepository { } /** - * Maps a list of {@link GridFSDBFile} to paged list of {@link DbArtifact}s. + * Maps a list of {@link GridFSDBFile} to a list of + * {@link AbstractDbArtifact}s. * * @param tenant * the tenant * @param dbFiles * the list of mongoDB gridFs files. - * @return a paged list of artifacts mapped from the given dbFiles + * @return list of artifacts mapped from the given dbFiles */ - private static List map(final List dbFiles) { + private static List map(final List dbFiles) { return dbFiles.stream().map(MongoDBArtifactStore::map).collect(Collectors.toList()); } @@ -233,14 +234,14 @@ public class MongoDBArtifactStore implements ArtifactRepository { * * @param ids * the ids of the files to lookup. - * @return list of artfiacts + * @return list of artifacts */ - public List getArtifactsByIds(final List ids) { + public List getArtifactsByIds(final List ids) { return map(gridFs.find(new Query().addCriteria(Criteria.where(ID).in(ids)))); } /** - * Maps a single {@link GridFSFile} to {@link DbArtifact}. + * Maps a single {@link GridFSFile} to {@link AbstractDbArtifact}. * * @param tenant * the tenant @@ -252,12 +253,8 @@ public class MongoDBArtifactStore implements ArtifactRepository { if (fsFile == null) { return null; } - final GridFsArtifact artifact = new GridFsArtifact(fsFile); - artifact.setArtifactId(fsFile.getId().toString()); - artifact.setSize(fsFile.getLength()); - artifact.setContentType(fsFile.getContentType()); - artifact.setHashes(new DbArtifactHash(fsFile.getFilename(), fsFile.getMD5())); - return artifact; + + return new GridFsArtifact(fsFile); } @Override diff --git a/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Artifact.java b/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Artifact.java index 1e1da2543..9fe130ccf 100644 --- a/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Artifact.java +++ b/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Artifact.java @@ -10,21 +10,28 @@ package org.eclipse.hawkbit.artifact.repository; import java.io.InputStream; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; +import org.springframework.util.Assert; import com.amazonaws.services.s3.AmazonS3; /** - * An {@link DbArtifact} implementation which retrieves the {@link InputStream} - * from the {@link AmazonS3} client. + * An {@link AbstractDbArtifact} implementation which retrieves the + * {@link InputStream} from the {@link AmazonS3} client. */ -public class S3Artifact extends DbArtifact { +public class S3Artifact extends AbstractDbArtifact { private final AmazonS3 amazonS3; private final S3RepositoryProperties s3Properties; private final String key; - S3Artifact(final AmazonS3 amazonS3, final S3RepositoryProperties s3Properties, final String key) { + S3Artifact(final AmazonS3 amazonS3, final S3RepositoryProperties s3Properties, final String key, + final String artifactId, final DbArtifactHash hashes, final Long size, final String contentType) { + super(artifactId, hashes, size, contentType); + Assert.notNull(amazonS3, "S3 cannot be null"); + Assert.notNull(s3Properties, "Properties cannot be null"); + Assert.notNull(key, "Key cannot be null"); this.amazonS3 = amazonS3; this.s3Properties = s3Properties; this.key = key; diff --git a/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Repository.java b/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Repository.java index f522ef215..86eb03402 100644 --- a/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Repository.java +++ b/extensions/hawkbit-extension-artifact-repository-s3/src/main/java/org/eclipse/hawkbit/artifact/repository/S3Repository.java @@ -20,7 +20,7 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,7 +77,7 @@ public class S3Repository implements ArtifactRepository { } @Override - public DbArtifact store(final String tenant, final InputStream content, final String filename, + public AbstractDbArtifact store(final String tenant, final InputStream content, final String filename, final String contentType) { return store(tenant, content, filename, contentType, null); } @@ -86,7 +86,7 @@ public class S3Repository implements ArtifactRepository { // suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 // is not used security related @SuppressWarnings("squid:S2070") - public DbArtifact store(final String tenant, final InputStream content, final String filename, + public AbstractDbArtifact store(final String tenant, final InputStream content, final String filename, final String contentType, final DbArtifactHash hash) { final MessageDigest mdSHA1; final MessageDigest mdMD5; @@ -117,7 +117,7 @@ public class S3Repository implements ArtifactRepository { } } - private DbArtifact store(final String tenant, final String sha1Hash16, final String mdMD5Hash16, + private AbstractDbArtifact store(final String tenant, final String sha1Hash16, final String mdMD5Hash16, final String contentType, final File file, final DbArtifactHash hash) { final S3Artifact s3Artifact = createS3Artifact(tenant, sha1Hash16, mdMD5Hash16, contentType, file); checkHashes(s3Artifact, hash); @@ -145,14 +145,8 @@ public class S3Repository implements ArtifactRepository { private S3Artifact createS3Artifact(final String tenant, final String sha1Hash, final String mdMD5Hash16, final String contentType, final File file) { - final String key = objectKey(tenant, sha1Hash); - final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, key); - s3Artifact.setContentType(contentType); - s3Artifact.setArtifactId(sha1Hash); - s3Artifact.setSize(file.length()); - s3Artifact.setContentType(contentType); - s3Artifact.setHashes(new DbArtifactHash(sha1Hash, mdMD5Hash16)); - return s3Artifact; + return new S3Artifact(amazonS3, s3Properties, objectKey(tenant, sha1Hash), sha1Hash, + new DbArtifactHash(sha1Hash, mdMD5Hash16), file.length(), contentType); } private ObjectMetadata createObjectMetadata(final String mdMD5Hash16, final String contentType, final File file) { @@ -181,7 +175,7 @@ public class S3Repository implements ArtifactRepository { } @Override - public DbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) { + public AbstractDbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) { final String key = objectKey(tenant, sha1Hash); LOG.info("Retrieving S3 object from bucket {} and key {}", s3Properties.getBucketName(), key); @@ -192,17 +186,15 @@ public class S3Repository implements ArtifactRepository { final ObjectMetadata s3ObjectMetadata = s3Object.getObjectMetadata(); - final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, key); - s3Artifact.setArtifactId(sha1Hash); - s3Artifact.setSize(s3ObjectMetadata.getContentLength()); // the MD5Content is stored in the ETag - s3Artifact.setHashes(new DbArtifactHash(sha1Hash, - BaseEncoding.base16().lowerCase().encode(BaseEncoding.base64().decode(s3ObjectMetadata.getETag())))); - s3Artifact.setContentType(s3ObjectMetadata.getContentType()); - return s3Artifact; + return new S3Artifact(amazonS3, s3Properties, key, sha1Hash, + new DbArtifactHash(sha1Hash, + BaseEncoding.base16().lowerCase() + .encode(BaseEncoding.base64().decode(s3ObjectMetadata.getETag()))), + s3ObjectMetadata.getContentLength(), s3ObjectMetadata.getContentType()); } - private static void checkHashes(final DbArtifact artifact, final DbArtifactHash hash) { + private static void checkHashes(final AbstractDbArtifact artifact, final DbArtifactHash hash) { if (hash == null) { return; } diff --git a/extensions/hawkbit-extension-artifact-repository-s3/src/test/java/org/eclipse/hawkbit/artifact/repository/S3RepositoryTest.java b/extensions/hawkbit-extension-artifact-repository-s3/src/test/java/org/eclipse/hawkbit/artifact/repository/S3RepositoryTest.java index 6eb79e2bd..e3855bb96 100644 --- a/extensions/hawkbit-extension-artifact-repository-s3/src/test/java/org/eclipse/hawkbit/artifact/repository/S3RepositoryTest.java +++ b/extensions/hawkbit-extension-artifact-repository-s3/src/test/java/org/eclipse/hawkbit/artifact/repository/S3RepositoryTest.java @@ -26,7 +26,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.junit.Before; import org.junit.Test; @@ -117,7 +117,7 @@ public class S3RepositoryTest { when(s3ObjectMetadataMock.getContentType()).thenReturn(knownContentType); // test - final DbArtifact artifactBySha1 = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash); + final AbstractDbArtifact artifactBySha1 = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash); // verify assertThat(artifactBySha1.getArtifactId()).isEqualTo(knownSHA1Hash); @@ -152,7 +152,7 @@ public class S3RepositoryTest { when(amazonS3Mock.getObject(s3Properties.getBucketName(), knownSHA1Hash)).thenReturn(null); // test - final DbArtifact artifactBySha1NotExists = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash); + final AbstractDbArtifact artifactBySha1NotExists = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash); // verify assertThat(artifactBySha1NotExists).isNull(); diff --git a/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystem.java b/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystem.java index fed6acc86..7507a6c0e 100644 --- a/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystem.java +++ b/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystem.java @@ -14,19 +14,24 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; +import org.springframework.util.Assert; import com.google.common.base.Throwables; /** - * A {@link DbArtifact} implementation which dynamically creates a + * {@link AbstractDbArtifact} implementation which dynamically creates a * {@link FileInputStream} on calling {@link #getFileInputStream()}. */ -public class ArtifactFilesystem extends DbArtifact { +public class ArtifactFilesystem extends AbstractDbArtifact { private final File file; - ArtifactFilesystem(final File file) { + public ArtifactFilesystem(final File file, final String artifactId, final DbArtifactHash hashes, final Long size, + final String contentType) { + super(artifactId, hashes, size, contentType); + Assert.notNull(file, "File cannot be null"); this.file = file; } diff --git a/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepository.java b/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepository.java index abf6b1510..80bf108e1 100644 --- a/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepository.java +++ b/hawkbit-artifact-repository-filesystem/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepository.java @@ -22,7 +22,7 @@ import java.security.NoSuchAlgorithmException; import java.util.List; import org.apache.commons.io.FileUtils; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +88,7 @@ public class ArtifactFilesystemRepository implements ArtifactRepository { } final File file = createTempFile(); - final DbArtifact artifact = store(content, contentType, hash, mdSHA1, mdMD5, file); + final AbstractDbArtifact artifact = store(content, contentType, hash, mdSHA1, mdMD5, file); return renameFileToSHA1Naming(tenant, file, artifact); } @@ -103,25 +103,22 @@ public class ArtifactFilesystemRepository implements ArtifactRepository { if (!file.exists()) { return null; } - final ArtifactFilesystem artifact = new ArtifactFilesystem(file); - artifact.setArtifactId(sha1); - artifact.setHashes(new DbArtifactHash(sha1, null)); - artifact.setSize(file.length()); - return artifact; + + return new ArtifactFilesystem(file, sha1, new DbArtifactHash(sha1, null), file.length(), null); } - private DbArtifact store(final InputStream content, final String contentType, final DbArtifactHash hash, + private AbstractDbArtifact store(final InputStream content, final String contentType, final DbArtifactHash hash, final MessageDigest mdSHA1, final MessageDigest mdMD5, final File file) { - final DbArtifact artifact = new DbArtifact(); + AbstractDbArtifact artifact; try (final DigestOutputStream outputstream = openFileOutputStream(file, mdSHA1, mdMD5)) { final long artifactSize = ByteStreams.copy(content, outputstream); outputstream.flush(); final String sha1Hash = BaseEncoding.base16().lowerCase().encode(mdSHA1.digest()); final String md5Hash = BaseEncoding.base16().lowerCase().encode(mdMD5.digest()); - artifact.setArtifactId(sha1Hash); - artifact.setSize(artifactSize); - artifact.setContentType(contentType); - artifact.setHashes(new DbArtifactHash(sha1Hash, md5Hash)); + + artifact = new ArtifactFilesystem(file, sha1Hash, new DbArtifactHash(sha1Hash, md5Hash), artifactSize, + contentType); + checkHashes(artifact, hash); } catch (final IOException e) { throw new ArtifactStoreException(e.getMessage(), e); @@ -134,9 +131,11 @@ public class ArtifactFilesystemRepository implements ArtifactRepository { return artifact; } - private ArtifactFilesystem renameFileToSHA1Naming(final String tenant, final File file, final DbArtifact artifact) { + private ArtifactFilesystem renameFileToSHA1Naming(final String tenant, final File file, + final AbstractDbArtifact artifact) { final File fileSHA1Naming = getFile(tenant, artifact.getHashes().getSha1()); - final ArtifactFilesystem fileSystemArtifact = new ArtifactFilesystem(fileSHA1Naming); + final ArtifactFilesystem fileSystemArtifact = new ArtifactFilesystem(fileSHA1Naming, artifact.getArtifactId(), + artifact.getHashes(), artifact.getSize(), artifact.getContentType()); if (fileSHA1Naming.exists()) { FileUtils.deleteQuietly(file); } else { @@ -150,14 +149,11 @@ public class ArtifactFilesystemRepository implements ArtifactRepository { if (!file.delete()) { LOG.debug("Could not delete temp file {}", file); } - fileSystemArtifact.setArtifactId(artifact.getArtifactId()); - fileSystemArtifact.setContentType(artifact.getContentType()); - fileSystemArtifact.setHashes(artifact.getHashes()); - fileSystemArtifact.setSize(artifact.getSize()); + return fileSystemArtifact; } - private DbArtifact checkHashes(final DbArtifact artifact, final DbArtifactHash hash) { + private AbstractDbArtifact checkHashes(final AbstractDbArtifact artifact, final DbArtifactHash hash) { if (hash == null) { return artifact; } diff --git a/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepositoryTest.java b/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepositoryTest.java index baef32bb5..8048dabf2 100644 --- a/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepositoryTest.java +++ b/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemRepositoryTest.java @@ -16,7 +16,7 @@ import java.util.Random; import org.apache.commons.io.IOUtils; import org.assertj.core.api.Assertions; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.junit.Test; import ru.yandex.qatools.allure.annotations.Description; @@ -52,7 +52,7 @@ public class ArtifactFilesystemRepositoryTest { final byte[] fileContent = randomBytes(); final ArtifactFilesystem artifact = storeRandomArtifact(fileContent); - final DbArtifact artifactBySha1 = artifactFilesystemRepository.getArtifactBySha1(TENANT, + final AbstractDbArtifact artifactBySha1 = artifactFilesystemRepository.getArtifactBySha1(TENANT, artifact.getHashes().getSha1()); assertThat(artifactBySha1).isNotNull(); } diff --git a/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemTest.java b/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemTest.java index f9e3dcac0..827d57762 100644 --- a/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemTest.java +++ b/hawkbit-artifact-repository-filesystem/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactFilesystemTest.java @@ -16,6 +16,7 @@ import java.io.IOException; import org.apache.commons.io.IOUtils; import org.assertj.core.api.Assertions; +import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.junit.Test; import ru.yandex.qatools.allure.annotations.Description; @@ -30,7 +31,8 @@ public class ArtifactFilesystemTest { @Description("Verifies that an exception is thrown on opening an InputStream when file does not exists") public void getInputStreamOfNonExistingFileThrowsException() { final File file = new File("fileWhichTotalDoesNotExists"); - final ArtifactFilesystem underTest = new ArtifactFilesystem(file); + final ArtifactFilesystem underTest = new ArtifactFilesystem(file, "fileWhichTotalDoesNotExists", + new DbArtifactHash("1", "2"), 0L, null); try { underTest.getFileInputStream(); Assertions.fail("Expected a FileNotFoundException because file does not exists"); @@ -45,7 +47,8 @@ public class ArtifactFilesystemTest { final File createTempFile = File.createTempFile(ArtifactFilesystemTest.class.getSimpleName(), ""); createTempFile.deleteOnExit(); - final ArtifactFilesystem underTest = new ArtifactFilesystem(createTempFile); + final ArtifactFilesystem underTest = new ArtifactFilesystem(createTempFile, + ArtifactFilesystemTest.class.getSimpleName(), new DbArtifactHash("1", "2"), 0L, null); final byte[] buffer = new byte[1024]; IOUtils.read(underTest.getFileInputStream(), buffer); } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactRepository.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactRepository.java index a62e7942b..5540bf150 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactRepository.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactRepository.java @@ -12,7 +12,7 @@ import java.io.InputStream; import javax.validation.constraints.NotNull; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.hibernate.validator.constraints.NotEmpty; @@ -42,7 +42,7 @@ public interface ArtifactRepository { * @throws ArtifactStoreException * in case storing of the artifact was not successful */ - DbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename, + AbstractDbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename, String contentType); /** @@ -69,7 +69,7 @@ public interface ArtifactRepository { * in case {@code hash} is provided and not matching to the * calculated hashes during storing */ - DbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename, + AbstractDbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename, String contentType, DbArtifactHash hash); /** @@ -86,7 +86,7 @@ public interface ArtifactRepository { void deleteBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash); /** - * Retrieves a {@link DbArtifact} from the store by it's SHA1 hash. + * Retrieves a {@link AbstractDbArtifact} from the store by it's SHA1 hash. * * @param tenant * the tenant to store the artifact @@ -97,7 +97,7 @@ public interface ArtifactRepository { * @throws MethodNotSupportedException * if implementation does not support the operation */ - DbArtifact getArtifactBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash); + AbstractDbArtifact getArtifactBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash); /** * Deletes all artifacts of given tenant. diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/AbstractDbArtifact.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/AbstractDbArtifact.java new file mode 100644 index 000000000..6746c1d64 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/AbstractDbArtifact.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.artifact.repository.model; + +import java.io.InputStream; + +import org.springframework.util.Assert; + +/** + * Database representation of artifact. + * + */ +public abstract class AbstractDbArtifact { + + private final String artifactId; + private final DbArtifactHash hashes; + private final long size; + private final String contentType; + + protected AbstractDbArtifact(final String artifactId, final DbArtifactHash hashes, final long size, + final String contentType) { + Assert.notNull(artifactId, "Artifact ID cannot be null"); + Assert.notNull(hashes, "Hashes cannot be null"); + this.artifactId = artifactId; + this.hashes = hashes; + this.size = size; + this.contentType = contentType; + } + + /** + * @return ID of the artifact + */ + public String getArtifactId() { + return artifactId; + } + + /** + * @return hashes of the artifact + */ + public DbArtifactHash getHashes() { + return hashes; + } + + /** + * @return site of the artifact in bytes + */ + public long getSize() { + return size; + } + + /** + * @return content-type if known by the repository or null + */ + public String getContentType() { + return contentType; + } + + /** + * Creates an {@link InputStream} on this artifact. Caller has to take care + * of closing the stream. Repeatable calls open a new {@link InputStream}. + * + * @return {@link InputStream} to read from artifact. + */ + public abstract InputStream getFileInputStream(); +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifact.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifact.java deleted file mode 100644 index 2385e0dfa..000000000 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifact.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.artifact.repository.model; - -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Database representation of artifact. - * - */ -public class DbArtifact { - - private String artifactId; - - private DbArtifactHash hashes; - - private Long size; - - private String contentType; - - private InputStream fileInputStream; - - private OutputStream fileOutputStream; - - public void setArtifactId(final String artifactId) { - this.artifactId = artifactId; - } - - public String getArtifactId() { - return artifactId; - } - - public DbArtifactHash getHashes() { - return hashes; - } - - public void setHashes(final DbArtifactHash hashes) { - this.hashes = hashes; - } - - public Long getSize() { - return size; - } - - public void setSize(final Long size) { - this.size = size; - } - - public void setContentType(final String contentType) { - this.contentType = contentType; - } - - public String getContentType() { - return contentType; - } - - public void setFileInputStream(final InputStream fileInputStream) { - this.fileInputStream = fileInputStream; - } - - public InputStream getFileInputStream() { - return fileInputStream; - } - - public OutputStream getFileOutputStream() { - return fileOutputStream; - } - - public void setFileOutputStream(final OutputStream fileOutputStream) { - this.fileOutputStream = fileOutputStream; - } -} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifactHash.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifactHash.java index 5619a2292..3a52f82aa 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifactHash.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/artifact/repository/model/DbArtifactHash.java @@ -14,9 +14,9 @@ package org.eclipse.hawkbit.artifact.repository.model; */ public class DbArtifactHash { - private String sha1; + private final String sha1; - private String md5; + private final String md5; /** * Constructor. @@ -27,19 +27,10 @@ public class DbArtifactHash { * the md5 hash */ public DbArtifactHash(final String sha1, final String md5) { - super(); this.sha1 = sha1; this.md5 = md5; } - public void setSha1(final String sha1) { - this.sha1 = sha1; - } - - public void setMd5(final String md5) { - this.md5 = md5; - } - public String getSha1() { return sha1; } diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java index a04ba535a..0cc0b93a6 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java @@ -8,12 +8,9 @@ */ package org.eclipse.hawkbit.ddi.rest.resource; -import java.io.IOException; import java.util.List; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.hawkbit.api.ApiType; import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.URLPlaceholder; @@ -35,8 +32,6 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.mvc.ControllerLinkBuilder; import org.springframework.http.HttpRequest; -import com.google.common.base.Charsets; - /** * Utility class for the DDI API. */ @@ -148,23 +143,4 @@ public final class DataConversionHelper { return result; } - static void writeMD5FileResponse(final String fileName, final HttpServletResponse response, final Artifact artifact) - throws IOException { - final StringBuilder builder = new StringBuilder(); - builder.append(artifact.getMd5Hash()); - builder.append(" "); - builder.append(fileName); - final byte[] content = builder.toString().getBytes(Charsets.US_ASCII); - - final StringBuilder header = new StringBuilder(); - header.append("attachment;filename="); - header.append(fileName); - header.append(DdiRestConstants.ARTIFACT_MD5_DWNL_SUFFIX); - - response.setContentLength(content.length); - response.setHeader("Content-Disposition", header.toString()); - - response.getOutputStream().write(content, 0, content.length); - } - } diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index a2fb1135f..279f882b9 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -17,7 +17,7 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.eclipse.hawkbit.api.ArtifactUrlHandler; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiActionHistory; import org.eclipse.hawkbit.ddi.json.model.DdiCancel; @@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; +import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException; @@ -47,8 +48,9 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.rest.util.FileStreamingUtil; +import org.eclipse.hawkbit.rest.util.HttpUtil; import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder; -import org.eclipse.hawkbit.rest.util.RestResourceConversionHelper; import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.util.IpUtil; @@ -56,7 +58,10 @@ import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Scope; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.server.ServletServerHttpRequest; @@ -80,6 +85,12 @@ public class DdiRootController implements DdiRootControllerRestApi { private static final Logger LOG = LoggerFactory.getLogger(DdiRootController.class); private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({})."; + @Autowired + private ApplicationEventPublisher eventPublisher; + + @Autowired + private ApplicationContext applicationContext; + @Autowired private ControllerManagement controllerManagement; @@ -142,7 +153,7 @@ public class DdiRootController implements DdiRootControllerRestApi { @PathVariable("controllerId") final String controllerId, @PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("fileName") final String fileName) { - ResponseEntity result; + final ResponseEntity result; final Target target = controllerManagement.findByControllerId(controllerId) .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); @@ -159,19 +170,26 @@ public class DdiRootController implements DdiRootControllerRestApi { @SuppressWarnings("squid:S3655") final Artifact artifact = module.getArtifactByFilename(fileName).get(); - final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) + final AbstractDbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) .orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash())); - final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match"); - if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { + final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader(HttpHeaders.IF_MATCH); + if (ifMatch != null && !HttpUtil.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); } else { final ActionStatus action = checkAndLogDownload(requestResponseContextHolder.getHttpServletRequest(), target, module.getId()); - result = RestResourceConversionHelper.writeFileResponse(artifact, + + final Long statusId = action.getId(); + + result = FileStreamingUtil.writeFileResponse(file, artifact.getFilename(), + artifact.getLastModifiedAt() != null ? artifact.getLastModifiedAt() : artifact.getCreatedAt(), requestResponseContextHolder.getHttpServletResponse(), - requestResponseContextHolder.getHttpServletRequest(), file, controllerManagement, - action.getId()); + requestResponseContextHolder.getHttpServletRequest(), + (length, shippedSinceLastEvent, total) -> eventPublisher + .publishEvent(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, + shippedSinceLastEvent, applicationContext.getId()))); + } } return result; @@ -218,15 +236,19 @@ public class DdiRootController implements DdiRootControllerRestApi { return ResponseEntity.notFound().build(); } + final Artifact artifact = module.getArtifactByFilename(fileName) + .orElseThrow(() -> new EntityNotFoundException(Artifact.class, fileName)); + try { - DataConversionHelper.writeMD5FileResponse(fileName, requestResponseContextHolder.getHttpServletResponse(), - module.getArtifactByFilename(fileName).get()); + FileStreamingUtil.writeMD5FileResponse(requestResponseContextHolder.getHttpServletResponse(), + artifact.getMd5Hash(), fileName); } catch (final IOException e) { LOG.error("Failed to stream MD5 File", e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } return ResponseEntity.ok().build(); + } @Override @@ -254,11 +276,11 @@ public class DdiRootController implements DdiRootControllerRestApi { final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT; - List actionHistoryMsgs = controllerManagement.getActionHistoryMessages(action.getId(), + final List actionHistoryMsgs = controllerManagement.getActionHistoryMessages(action.getId(), actionHistoryMessageCount == null ? Integer.parseInt(DdiRestConstants.NO_ACTION_HISTORY) : actionHistoryMessageCount); - DdiActionHistory actionHistory = actionHistoryMsgs.isEmpty() ? null + final DdiActionHistory actionHistory = actionHistoryMsgs.isEmpty() ? null : new DdiActionHistory(action.getStatus().name(), actionHistoryMsgs); final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()), diff --git a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java index c559903ad..b84de5467 100644 --- a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java +++ b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java @@ -112,7 +112,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { // test failed If-match mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()) - .header("If-Match", "fsjkhgjfdhg")) + .header(HttpHeaders.IF_MATCH, "fsjkhgjfdhg")) .andExpect(status().isPreconditionFailed()); // test invalid range @@ -311,6 +311,18 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { assertThat(result.getResponse().getContentAsByteArray()) .isEqualTo(Arrays.copyOfRange(random, 1000, resultLength)); + // Start download from file end fails + mvc.perform( + get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", + tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") + .header("Range", "bytes=" + random.length + "-")) + .andExpect(status().isRequestedRangeNotSatisfiable()) + .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) + .andExpect(header().string("Accept-Ranges", "bytes")) + .andExpect(header().string("Last-Modified", dateFormat.format(new Date(artifact.getCreatedAt())))) + .andExpect(header().string("Content-Range", "bytes */" + random.length)) + .andExpect(header().string("Content-Disposition", "attachment;filename=file1")); + // multipart download - first 20 bytes in 2 parts result = mvc .perform( diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java index 7fa084d22..504368b49 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java @@ -13,7 +13,7 @@ import java.util.Optional; import java.util.UUID; import org.eclipse.hawkbit.api.HostnameResolver; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.cache.DownloadArtifactCache; import org.eclipse.hawkbit.cache.DownloadIdCache; @@ -188,7 +188,7 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { return Optional.empty(); } - private static DmfArtifact convertDbArtifact(final DbArtifact dbArtifact) { + private static DmfArtifact convertDbArtifact(final AbstractDbArtifact dbArtifact) { final DmfArtifact artifact = new DmfArtifact(); artifact.setSize(dbArtifact.getSize()); final DbArtifactHash dbArtifactHash = dbArtifact.getHashes(); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index e26206954..f91c40824 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -16,11 +16,13 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.File; import java.net.URL; import java.util.Optional; import org.eclipse.hawkbit.api.HostnameResolver; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystem; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; @@ -160,9 +162,8 @@ public class AmqpControllerAuthenticationTest { when(artifactManagementMock.findArtifact(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(testArtifact)); - final DbArtifact artifact = new DbArtifact(); - artifact.setSize(ARTIFACT_SIZE); - artifact.setHashes(new DbArtifactHash(SHA1, "md5 test")); + final AbstractDbArtifact artifact = new ArtifactFilesystem(new File("does not exist"), SHA1, + new DbArtifactHash(SHA1, "md5 test"), ARTIFACT_SIZE, null); when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(Optional.of(artifact)); amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index 360f5ef59..82cf3eadc 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -17,6 +17,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; +import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -26,7 +27,9 @@ import java.util.UUID; import org.eclipse.hawkbit.api.ArtifactUrl; import org.eclipse.hawkbit.api.ArtifactUrlHandler; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystem; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; @@ -173,9 +176,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { public void testSendDownloadRequest() { DistributionSet dsA = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); SoftwareModule module = dsA.getModules().iterator().next(); - final List receivedList = new ArrayList<>(); + final List receivedList = new ArrayList<>(); for (final Artifact artifact : testdataFactory.createArtifacts(module.getId())) { - receivedList.add(new DbArtifact()); + receivedList.add(new ArtifactFilesystem(new File("./test"), artifact.getSha1Hash(), + new DbArtifactHash(artifact.getSha1Hash(), null), artifact.getSize(), null)); } module = softwareModuleManagement.findSoftwareModuleById(module.getId()).get(); dsA = distributionSetManagement.findDistributionSetById(dsA.getId()).get(); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 9cc315033..85892a3e1 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -28,7 +28,7 @@ import java.util.Optional; import org.eclipse.hawkbit.api.HostnameResolver; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; @@ -371,7 +371,7 @@ public class AmqpMessageHandlerServiceTest { final Artifact localArtifactMock = mock(Artifact.class); when(localArtifactMock.getSha1Hash()).thenReturn(SHA1); - final DbArtifact dbArtifactMock = mock(DbArtifact.class); + final AbstractDbArtifact dbArtifactMock = mock(AbstractDbArtifact.class); when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(localArtifactMock)); when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), SHA1)) .thenReturn(true); diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDownloadRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDownloadRestApi.java index e329b1127..a26812e7d 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDownloadRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDownloadRestApi.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.mgmt.rest.api; +import java.io.InputStream; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -37,7 +39,7 @@ public interface MgmtDownloadRestApi { */ @RequestMapping(method = RequestMethod.GET, value = MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING) @ResponseBody - ResponseEntity downloadArtifactByDownloadId(@PathVariable("tenant") String tenant, + ResponseEntity downloadArtifactByDownloadId(@PathVariable("tenant") String tenant, @PathVariable("downloadId") String downloadId); } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java index 2b72b50c8..42064fcc5 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java @@ -12,7 +12,7 @@ import java.io.InputStream; import javax.servlet.http.HttpServletRequest; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadArtifactRestApi; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; @@ -20,14 +20,17 @@ import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.rest.util.FileStreamingUtil; +import org.eclipse.hawkbit.rest.util.HttpUtil; import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder; -import org.eclipse.hawkbit.rest.util.RestResourceConversionHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; @@ -37,7 +40,6 @@ import org.springframework.web.context.WebApplicationContext; @RestController @Scope(value = WebApplicationContext.SCOPE_REQUEST) public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi { - @Autowired private SoftwareModuleManagement softwareModuleManagement; @@ -58,25 +60,25 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi * @return responseEntity with status ok if successful */ @Override - @ResponseBody public ResponseEntity downloadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("artifactId") final Long artifactId) { + final SoftwareModule module = softwareModuleManagement.findSoftwareModuleById(softwareModuleId) .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId)); final Artifact artifact = module.getArtifact(artifactId) .orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId)); - final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) + final AbstractDbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) .orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash())); final HttpServletRequest request = requestResponseContextHolder.getHttpServletRequest(); - final String ifMatch = request.getHeader("If-Match"); - if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { + final String ifMatch = request.getHeader(HttpHeaders.IF_MATCH); + if (ifMatch != null && !HttpUtil.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); } - return RestResourceConversionHelper.writeFileResponse(artifact, - requestResponseContextHolder.getHttpServletResponse(), request, file); - + return FileStreamingUtil.writeFileResponse(file, artifact.getFilename(), + artifact.getLastModifiedAt() != null ? artifact.getLastModifiedAt() : artifact.getCreatedAt(), + requestResponseContextHolder.getHttpServletResponse(), request, null); } } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadResource.java index e06cc3b11..e3b9193ce 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadResource.java @@ -8,33 +8,26 @@ */ package org.eclipse.hawkbit.mgmt.rest.resource; -import java.io.IOException; import java.io.InputStream; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.cache.DownloadArtifactCache; import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.cache.DownloadType; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadRestApi; +import org.eclipse.hawkbit.rest.util.FileStreamingUtil; import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; -import com.google.common.io.ByteStreams; -import com.google.common.net.HttpHeaders; - /** * A resource for download artifacts. */ @@ -55,8 +48,9 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi { @Override @ResponseBody - public ResponseEntity downloadArtifactByDownloadId(@PathVariable("tenant") final String tenant, + public ResponseEntity downloadArtifactByDownloadId(@PathVariable("tenant") final String tenant, @PathVariable("downloadId") final String downloadId) { + try { final DownloadArtifactCache artifactCache = downloadIdCache.get(downloadId); if (artifactCache == null) { @@ -64,7 +58,7 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi { return ResponseEntity.notFound().build(); } - DbArtifact artifact = null; + AbstractDbArtifact artifact = null; if (DownloadType.BY_SHA1.equals(artifactCache.getDownloadType())) { artifact = artifactRepository.getArtifactBySha1(tenant, artifactCache.getId()); @@ -78,24 +72,12 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi { return ResponseEntity.notFound().build(); } - final HttpServletResponse response = requestResponseContextHolder.getHttpServletResponse(); - final String etag = artifact.getHashes().getSha1(); - final long length = artifact.getSize(); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + downloadId); - response.setHeader(HttpHeaders.ETAG, etag); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - response.setContentLengthLong(length); + return FileStreamingUtil.writeFileResponse(artifact, downloadId, null, + requestResponseContextHolder.getHttpServletResponse(), + requestResponseContextHolder.getHttpServletRequest(), null); - try (InputStream inputstream = artifact.getFileInputStream()) { - ByteStreams.copy(inputstream, requestResponseContextHolder.getHttpServletResponse().getOutputStream()); - } catch (final IOException e) { - LOGGER.error("Cannot copy streams", e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } } finally { downloadIdCache.evict(downloadId); } - - return ResponseEntity.ok().build(); } } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java index 6ac1fcc80..01d95a25e 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java @@ -11,6 +11,8 @@ package org.eclipse.hawkbit.mgmt.rest.resource; import java.util.List; import java.util.stream.Collectors; +import javax.validation.ValidationException; + import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBody; @@ -26,7 +28,6 @@ import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; @@ -122,7 +123,7 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi { rolloutGroupConditions); } else { - throw new ConstraintViolationException("Either 'amountGroups' or 'groups' must be defined in the request"); + throw new ValidationException("Either 'amountGroups' or 'groups' must be defined in the request"); } return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout, true)); diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index aa91ff5a1..2710d7afe 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -13,6 +13,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import javax.validation.ValidationException; + import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut; @@ -29,7 +31,6 @@ import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; @@ -293,7 +294,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { } if (!MgmtActionType.FORCED.equals(actionUpdate.getForceType())) { - throw new ConstraintViolationException("Resource supports only switch to FORCED."); + throw new ValidationException("Resource supports only switch to FORCED."); } action = deploymentManagement.forceTargetAction(actionId); diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 960bb1af1..1e360d08c 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -32,12 +32,13 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.validation.ConstraintViolationException; + import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.ActionFields; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java index b43a78875..fc9b633eb 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java @@ -13,7 +13,7 @@ import java.util.Optional; import javax.validation.constraints.NotNull; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; @@ -196,15 +196,15 @@ public interface ArtifactManagement { Page findArtifactBySoftwareModule(@NotNull Pageable pageReq, @NotNull Long swId); /** - * Loads {@link DbArtifact} from store for given {@link Artifact}. + * Loads {@link AbstractDbArtifact} from store for given {@link Artifact}. * * @param sha1Hash * to search for - * @return loaded {@link DbArtifact} + * @return loaded {@link AbstractDbArtifact} * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DOWNLOAD_ARTIFACT + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_CONTROLLER_DOWNLOAD) - Optional loadArtifactBinary(@NotEmpty String sha1Hash); + Optional loadArtifactBinary(@NotEmpty String sha1Hash); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 8eadd1cc6..cf7c8db3a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -17,7 +17,6 @@ import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; -import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.QuotaExceededException; @@ -61,22 +60,6 @@ public interface ControllerManagement { @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) Action addCancelActionStatus(@NotNull ActionStatusCreate create); - /** - * Sends the download progress and notifies the event publisher with a - * {@link DownloadProgressEvent}. - * - * @param statusId - * the ID of the {@link ActionStatus} - * @param requestedBytes - * requested bytes of the request - * @param shippedBytesSinceLast - * since the last report - * @param shippedBytesOverall - * for the {@link ActionStatus} - */ - @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - void downloadProgress(Long statusId, Long requestedBytes, Long shippedBytesSinceLast, Long shippedBytesOverall); - /** * Simple addition of a new {@link ActionStatus} entry to the {@link Action} * . No state changes. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 3bf3dcdd6..2e169eaea 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -11,13 +11,13 @@ package org.eclipse.hawkbit.repository; import java.util.List; import java.util.Optional; +import javax.validation.ConstraintViolationException; import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.builder.RolloutUpdate; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/DownloadProgressEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/DownloadProgressEvent.java index 568bc5a28..97b4bbf51 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/DownloadProgressEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/DownloadProgressEvent.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.repository.event.remote; +import org.eclipse.hawkbit.repository.model.ActionStatus; + /** * TenantAwareEvent that contains an updated download progress for a given * ActionStatus that was written for a download request. @@ -17,7 +19,7 @@ public class DownloadProgressEvent extends RemoteTenantAwareEvent { private static final long serialVersionUID = 1L; - private Long shippedBytesSinceLast; + private long shippedBytesSinceLast; /** * Default constructor. @@ -31,13 +33,16 @@ public class DownloadProgressEvent extends RemoteTenantAwareEvent { * * @param tenant * the tenant + * @param actionStatusId + * of the {@link ActionStatus} the download belongs to * @param shippedBytesSinceLast * the shippedBytesSinceLast * @param applicationId * the application id. */ - public DownloadProgressEvent(final String tenant, final Long shippedBytesSinceLast, final String applicationId) { - super(shippedBytesSinceLast, tenant, applicationId); + public DownloadProgressEvent(final String tenant, final Long actionStatusId, final long shippedBytesSinceLast, + final String applicationId) { + super(actionStatusId, tenant, applicationId); this.shippedBytesSinceLast = shippedBytesSinceLast; } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ConstraintViolationException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ConstraintViolationException.java deleted file mode 100644 index 021d91526..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ConstraintViolationException.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.repository.exception; - -import java.util.stream.Collectors; - -import org.eclipse.hawkbit.exception.AbstractServerRtException; -import org.eclipse.hawkbit.exception.SpServerError; - -/** - * the {@link ConstraintViolationException} is thrown when an entity is tried to - * be saved which has constraint violations - */ -public class ConstraintViolationException extends AbstractServerRtException { - - private static final long serialVersionUID = 1L; - - private static final String MESSAGE_FORMATTER_SEPARATOR = " "; - - /** - * Constructor for {@link ConstraintViolationException} - * - * @param ex - * the javax.validation.ConstraintViolationException which is - * thrown - */ - public ConstraintViolationException(final javax.validation.ConstraintViolationException ex) { - this(getExceptionMessage(ex)); - } - - /** - * Creates a new {@link ConstraintViolationException} with the error code - * {@link SpServerError#SP_REPO_CONSTRAINT_VIOLATION}. - * - * @param msgText - * the message text for this exception - */ - public ConstraintViolationException(final String msgText) { - super(msgText, SpServerError.SP_REPO_CONSTRAINT_VIOLATION); - } - - /** - * Uses the information of - * {@link javax.validation.ConstraintViolationException} to provide a proper - * error message for {@link ConstraintViolationException} - * - * @param ex - * javax.validation.ConstraintViolationException which is thrown - * @return message String with proper error information - */ - public static String getExceptionMessage(final javax.validation.ConstraintViolationException ex) { - return ex - .getConstraintViolations().stream().map(violation -> violation.getPropertyPath() - + MESSAGE_FORMATTER_SEPARATOR + violation.getMessage() + ".") - .collect(Collectors.joining(MESSAGE_FORMATTER_SEPARATOR)); - } - -} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Artifact.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Artifact.java index b24943c5c..10aa519e5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Artifact.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Artifact.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.repository.model; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; /** * Binaries for a {@link SoftwareModule} Note: the decision which artifacts have @@ -34,7 +34,7 @@ public interface Artifact extends TenantAwareBaseEntity { /** * @return SHA-1 hash of the artifact in Base16 format that identifies the - * {@link DbArtifact} in the system. + * {@link AbstractDbArtifact} in the system. */ String getSha1Hash(); diff --git a/hawkbit-repository/hawkbit-repository-core/pom.xml b/hawkbit-repository/hawkbit-repository-core/pom.xml index a6b9dc62c..133d1c2a6 100644 --- a/hawkbit-repository/hawkbit-repository-core/pom.xml +++ b/hawkbit-repository/hawkbit-repository-core/pom.xml @@ -35,7 +35,7 @@ com.github.ben-manes.caffeine caffeine - + org.springframework spring-context-support diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java index 8df5e6035..950ec114c 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java @@ -12,7 +12,8 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; +import javax.validation.ValidationException; + import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -35,10 +36,10 @@ public final class RolloutHelper { */ public static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) { if (conditions.getSuccessCondition() == null) { - throw new ConstraintViolationException("Rollout group is missing success condition"); + throw new ValidationException("Rollout group is missing success condition"); } if (conditions.getSuccessAction() == null) { - throw new ConstraintViolationException("Rollout group is missing success action"); + throw new ValidationException("Rollout group is missing success action"); } } @@ -52,14 +53,14 @@ public final class RolloutHelper { */ public static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) { if (group.getTargetPercentage() < 1F || group.getTargetPercentage() > 100F) { - throw new ConstraintViolationException("Target percentage has to be between 1 and 100"); + throw new ValidationException("Target percentage has to be between 1 and 100"); } if (group.getSuccessCondition() == null) { - throw new ConstraintViolationException("Rollout group is missing success condition"); + throw new ValidationException("Rollout group is missing success condition"); } if (group.getSuccessAction() == null) { - throw new ConstraintViolationException("Rollout group is missing success action"); + throw new ValidationException("Rollout group is missing success action"); } return group; } @@ -74,9 +75,9 @@ public final class RolloutHelper { */ public static void verifyRolloutGroupParameter(final int amountGroup, final QuotaManagement quotaManagement) { if (amountGroup <= 0) { - throw new ConstraintViolationException("the amount of groups cannot be lower than zero"); + throw new ValidationException("the amount of groups cannot be lower than zero"); } else if (amountGroup > quotaManagement.getMaxRolloutGroupsPerRollout()) { - throw new ConstraintViolationException("the amount of groups cannot be greater than 500"); + throw new ValidationException("the amount of groups cannot be greater than 500"); } } @@ -88,9 +89,9 @@ public final class RolloutHelper { */ public static void verifyRolloutGroupTargetPercentage(final float percentage) { if (percentage <= 0) { - throw new ConstraintViolationException("the percentage must be greater than zero"); + throw new ValidationException("the percentage must be greater than zero"); } else if (percentage > 100) { - throw new ConstraintViolationException("the percentage must not be greater than 100"); + throw new ValidationException("the percentage must not be greater than 100"); } } @@ -240,11 +241,10 @@ public final class RolloutHelper { */ public static void verifyRemainingTargets(final long targetCount) { if (targetCount > 0) { - throw new ConstraintViolationException( - "Rollout groups don't match all targets that are targeted by the rollout"); + throw new ValidationException("Rollout groups don't match all targets that are targeted by the rollout"); } if (targetCount != 0) { - throw new ConstraintViolationException("Rollout groups target count verification failed"); + throw new ValidationException("Rollout groups target count verification failed"); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java index 76d8b8a29..900faf890 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java @@ -14,7 +14,7 @@ import java.util.Optional; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException; import org.eclipse.hawkbit.artifact.repository.HashNotMatchException; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; @@ -88,7 +88,7 @@ public class JpaArtifactManagement implements ArtifactManagement { public Artifact createArtifact(final InputStream stream, final Long moduleId, final String filename, final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting, final String contentType) { - DbArtifact result = null; + AbstractDbArtifact result = null; final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId); @@ -184,12 +184,12 @@ public class JpaArtifactManagement implements ArtifactManagement { } @Override - public Optional loadArtifactBinary(final String sha1Hash) { + public Optional loadArtifactBinary(final String sha1Hash) { return Optional.ofNullable(artifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), sha1Hash)); } private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename, - final DbArtifact result, final Artifact existing) { + final AbstractDbArtifact result, final Artifact existing) { JpaArtifact artifact = (JpaArtifact) existing; if (existing == null) { artifact = new JpaArtifact(result.getHashes().getSha1(), providedFilename, softwareModule); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index e4ce85e09..d6fd63f71 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; -import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; @@ -483,13 +482,6 @@ public class JpaControllerManagement implements ControllerManagement { .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId)); } - @Override - public void downloadProgress(final Long statusId, final Long requestedBytes, final Long shippedBytesSinceLast, - final Long shippedBytesOverall) { - eventPublisher.publishEvent(new DownloadProgressEvent(tenantAware.getCurrentTenant(), shippedBytesSinceLast, - applicationContext.getId())); - } - @Override public Optional findByControllerId(final String controllerId) { return targetRepository.findByControllerId(controllerId); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index 9e69f2671..6a7cbd9d9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -19,6 +19,7 @@ import java.util.stream.StreamSupport; import javax.persistence.EntityManager; import javax.validation.ConstraintDeclarationException; +import javax.validation.ValidationException; import org.eclipse.hawkbit.repository.AbstractRolloutManagement; import org.eclipse.hawkbit.repository.DeploymentManagement; @@ -37,7 +38,6 @@ import org.eclipse.hawkbit.repository.builder.RolloutUpdate; import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; @@ -216,7 +216,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery()); if (totalTargets == 0) { - throw new ConstraintViolationException("Rollout does not match any existing targets"); + throw new ValidationException("Rollout does not match any existing targets"); } rollout.setTotalTargets(totalTargets); return rolloutRepository.save(rollout); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaSoftwareModuleCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaSoftwareModuleCreate.java index 787678421..1f30c7fe9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaSoftwareModuleCreate.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaSoftwareModuleCreate.java @@ -8,10 +8,11 @@ */ package org.eclipse.hawkbit.repository.jpa.builder; +import javax.validation.ValidationException; + import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; import org.eclipse.hawkbit.repository.builder.AbstractSoftwareModuleUpdateCreate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; @@ -36,7 +37,7 @@ public class JpaSoftwareModuleCreate extends AbstractSoftwareModuleUpdateCreate< private SoftwareModuleType getSoftwareModuleTypeFromKeyString(final String type) { if (type == null) { - throw new ConstraintViolationException("type cannot be null"); + throw new ValidationException("type cannot be null"); } return softwareModuleTypeManagement.findSoftwareModuleTypeByKey(type.trim()) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java index 47e663c83..3f0d79de6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java @@ -21,7 +21,7 @@ import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.hibernate.validator.constraints.NotEmpty; @@ -71,9 +71,9 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art * Constructs artifact. * * @param sha1Hash - * that is the link to the {@link DbArtifact} entity. + * that is the link to the {@link AbstractDbArtifact} entity. * @param filename - * that is used by {@link DbArtifact} store. + * that is used by {@link AbstractDbArtifact} store. * @param softwareModule * of this artifact */ diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java index 022f26d0f..df8828ab5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java @@ -29,7 +29,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest { @Test @Description("Verifies that the download progress reloading by remote events works") public void reloadDownloadProgessByRemoteEvent() { - final DownloadProgressEvent downloadProgressEvent = new DownloadProgressEvent("DEFAULT", 3L, "Node"); + final DownloadProgressEvent downloadProgressEvent = new DownloadProgressEvent("DEFAULT", 1L, 3L, "Node"); DownloadProgressEvent remoteEvent = (DownloadProgressEvent) createProtoStuffEvent(downloadProgressEvent); assertThat(downloadProgressEvent).isEqualTo(remoteEvent); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java index e0b186812..2b1725db5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java @@ -26,7 +26,6 @@ public class ActionTest { @Description("Ensures that timeforced moded switch from soft to forces after defined timeframe.") public void timeforcedHitNewHasCodeIsGenerated() throws InterruptedException { - final boolean active; // current time + 1 seconds final long sleepTime = 1000; final long timeForceTimeAt = System.currentTimeMillis() + sleepTime; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java index 1933ab96e..31a1c064a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java @@ -104,10 +104,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { final Artifact result = artifactManagement.createArtifact(new ByteArrayInputStream(random), sm.getId(), "file1", false); - final Artifact result11 = artifactManagement.createArtifact(new ByteArrayInputStream(random), sm.getId(), - "file11", false); - final Artifact result12 = artifactManagement.createArtifact(new ByteArrayInputStream(random), sm.getId(), - "file12", false); + artifactManagement.createArtifact(new ByteArrayInputStream(random), sm.getId(), "file11", false); + artifactManagement.createArtifact(new ByteArrayInputStream(random), sm.getId(), "file12", false); final Artifact result2 = artifactManagement.createArtifact(new ByteArrayInputStream(random), sm2.getId(), "file2", false); @@ -270,8 +268,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { assertThat(artifactManagement.findArtifactBySoftwareModule(PAGE, sm.getId())).isEmpty(); - final Artifact result = artifactManagement.createArtifact(new RandomGeneratedInputStream(5 * 1024), sm.getId(), - "file1", false); + artifactManagement.createArtifact(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", false); assertThat(artifactManagement.findArtifactBySoftwareModule(PAGE, sm.getId())).hasSize(1); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index 2b69d87ed..6fd8ff33b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -747,7 +747,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { } @Test - @Description("Deletes multiple targets and verfies that all related metadat is also deleted.") + @Description("Deletes multiple targets and verfies that all related metadata is also deleted.") public void deletesTargetsAndVerifyCascadeDeletes() { final String undeployedTargetPrefix = "undep-T"; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java index 5a3758195..15e3b2511 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java @@ -21,6 +21,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import javax.validation.ValidationException; + import org.assertj.core.api.Condition; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.builder.RolloutCreate; @@ -37,7 +39,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; -import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; @@ -139,7 +140,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Expect(type = RolloutUpdatedEvent.class, count = 1), @Expect(type = TargetCreatedEvent.class, count = 10) }) public void entityQueriesReferringToNotExistingEntitiesThrowsException() { - final Rollout createdRollout = testdataFactory.createRollout("xxx"); + testdataFactory.createRollout("xxx"); verifyThrownExceptionBy(() -> rolloutManagement.deleteRollout(NOT_EXIST_IDL), "Rollout"); @@ -1089,7 +1090,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName); - assertThatExceptionOfType(ConstraintViolationException.class) + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> testdataFactory.createRolloutByVariables(rolloutName, "desc", amountGroups, "id==notExisting", distributionSet, successCondition, errorCondition)) .withMessageContaining("does not match any existing"); @@ -1336,7 +1337,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutGroups.add(generateRolloutGroup(0, percentTargetsInGroup1, null)); rolloutGroups.add(generateRolloutGroup(1, percentTargetsInGroup2, null)); - assertThatExceptionOfType(ConstraintViolationException.class) + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> rolloutManagement.createRollout(myRollout, rolloutGroups, conditions)) .withMessageContaining("groups don't match"); @@ -1357,7 +1358,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { generateRolloutGroup(0, percentTargetsInGroup1, null), generateRolloutGroup(1, percentTargetsInGroup2, null)); - assertThatExceptionOfType(ConstraintViolationException.class) + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> rolloutManagement.createRollout(myRollout, rolloutGroups, conditions)) .withMessageContaining("percentage has to be between 1 and 100"); @@ -1373,7 +1374,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build(); final RolloutCreate myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout); - assertThatExceptionOfType(ConstraintViolationException.class) + assertThatExceptionOfType(ValidationException.class) .isThrownBy(() -> rolloutManagement.createRollout(myRollout, illegalGroupAmount, conditions)) .withMessageContaining("not be greater than " + quotaManagement.getMaxRolloutGroupsPerRollout()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java index 9b9093d99..f1a73d50a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java @@ -252,8 +252,8 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { @Description("Counts all software modules in the repsitory that are not marked as deleted.") public void countSoftwareModulesAll() { // found in test - final SoftwareModule one = testdataFactory.createSoftwareModuleOs("one"); - final SoftwareModule two = testdataFactory.createSoftwareModuleOs("two"); + testdataFactory.createSoftwareModuleOs("one"); + testdataFactory.createSoftwareModuleOs("two"); final SoftwareModule deleted = testdataFactory.createSoftwareModuleOs("deleted"); // ignored softwareModuleManagement.deleteSoftwareModule(deleted.getId()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 6a281a466..4a4222ace 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -484,8 +484,6 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { */ private void checkTargetHasTags(final boolean strict, final Iterable targets, final TargetTag... tags) { _target: for (final Target tl : targets) { - final Target t = targetManagement.findTargetByControllerID(tl.getControllerId()).get(); - for (final Tag tt : tagManagement.findAllTargetTags(PAGE, tl.getControllerId())) { for (final Tag tag : tags) { if (tag.getName().equals(tt.getName())) { @@ -502,7 +500,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { private void checkTargetHasNotTags(final Iterable targets, final TargetTag... tags) { for (final Target tl : targets) { - final Target t = targetManagement.findTargetByControllerID(tl.getControllerId()).get(); + targetManagement.findTargetByControllerID(tl.getControllerId()).get(); for (final Tag tag : tags) { for (final Tag tt : tagManagement.findAllTargetTags(PAGE, tl.getControllerId())) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java index 3fcc258a1..b340c0f03 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtilityTest.java @@ -315,8 +315,8 @@ public class RSQLUtilityTest { .thenReturn(mock(Predicate.class)); // test - final Predicate result = RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup()) - .toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); + RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup()).toPredicate(baseSoftwareModuleRootMock, + criteriaQueryMock, criteriaBuilderMock); // verification verify(macroResolver).lookup(overdueProp); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java index fdb840fcc..15cb6a083 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java @@ -14,8 +14,6 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mockStatic; -import java.time.Instant; - import org.apache.commons.lang3.text.StrSubstitutor; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TimestampCalculator; @@ -48,8 +46,6 @@ public class VirtualPropertyResolverTest { private StrSubstitutor substitutor; - private Long nowTestTime; - private static final TenantConfigurationValue TEST_POLLING_TIME_INTERVAL = TenantConfigurationValue . builder().value("00:05:00").build(); private static final TenantConfigurationValue TEST_POLLING_OVERDUE_TIME_INTERVAL = TenantConfigurationValue @@ -57,7 +53,6 @@ public class VirtualPropertyResolverTest { @Before public void before() { - nowTestTime = Instant.now().toEpochMilli(); when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class)) .thenReturn(TEST_POLLING_TIME_INTERVAL); when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class)) diff --git a/hawkbit-rest-core/pom.xml b/hawkbit-rest-core/pom.xml index 6bd07e2a0..365c54680 100644 --- a/hawkbit-rest-core/pom.xml +++ b/hawkbit-rest-core/pom.xml @@ -21,22 +21,34 @@ - - org.eclipse.hawkbit - hawkbit-repository-api - ${project.version} - - - org.eclipse.hawkbit - hawkbit-repository-jpa - ${project.version} - org.eclipse.hawkbit hawkbit-core ${project.version} + + com.google.guava + guava + + + org.apache.commons + commons-lang3 + + + + org.springframework + spring-web + + + org.springframework.hateoas + spring-hateoas + + + org.springframework.data + spring-data-commons + + com.fasterxml.jackson.core jackson-core @@ -52,23 +64,29 @@ - + org.eclipse.hawkbit hawkbit-repository-test ${project.version} test - + + + org.eclipse.hawkbit + hawkbit-repository-jpa + ${project.version} + test + ru.yandex.qatools.allure allure-junit-adaptor test - + org.springframework.boot spring-boot-starter-test test - + org.json json test diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/data/SortDirection.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/data/SortDirection.java index ca33d25b3..72527d0c4 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/data/SortDirection.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/data/SortDirection.java @@ -12,9 +12,6 @@ import org.eclipse.hawkbit.rest.exception.SortParameterUnsupportedDirectionExcep /** * A definition of possible sorting direction. - * - * - * */ public enum SortDirection { /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MultiPartFileUploadException.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/MultiPartFileUploadException.java similarity index 94% rename from hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MultiPartFileUploadException.java rename to hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/MultiPartFileUploadException.java index 94bb05425..eeca421ac 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/MultiPartFileUploadException.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/MultiPartFileUploadException.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.repository.exception; +package org.eclipse.hawkbit.rest.exception; import org.eclipse.hawkbit.exception.AbstractServerRtException; import org.eclipse.hawkbit.exception.SpServerError; diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java index c36d2e3a8..79f0601a0 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java @@ -11,14 +11,15 @@ package org.eclipse.hawkbit.rest.exception; import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.hawkbit.exception.AbstractServerRtException; import org.eclipse.hawkbit.exception.SpServerError; -import org.eclipse.hawkbit.repository.exception.MultiPartFileUploadException; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,8 @@ public class ResponseExceptionHandler { private static final Map ERROR_TO_HTTP_STATUS = new EnumMap<>(SpServerError.class); private static final HttpStatus DEFAULT_RESPONSE_STATUS = HttpStatus.INTERNAL_SERVER_ERROR; + private static final String MESSAGE_FORMATTER_SEPARATOR = " "; + static { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_NOT_EXISTS, HttpStatus.NOT_FOUND); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_ALRREADY_EXISTS, HttpStatus.CONFLICT); @@ -138,12 +141,40 @@ public class ResponseExceptionHandler { */ @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity handleConstraintViolationException(final HttpServletRequest request, - final Exception ex) { + final ConstraintViolationException ex) { logRequest(request, ex); - final ExceptionInfo response = createExceptionInfo( - new org.eclipse.hawkbit.repository.exception.ConstraintViolationException( - (ConstraintViolationException) ex)); + final ExceptionInfo response = new ExceptionInfo(); + response.setMessage(ex.getConstraintViolations().stream().map( + violation -> violation.getPropertyPath() + MESSAGE_FORMATTER_SEPARATOR + violation.getMessage() + ".") + .collect(Collectors.joining(MESSAGE_FORMATTER_SEPARATOR))); + response.setExceptionClass(ex.getClass().getName()); + response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey()); + + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + + /** + * Method for handling exception of type ValidationException which is thrown + * in case the request is rejected due to invalid requests. Called by the + * Spring-Framework for exception handling. + * + * @param request + * the Http request + * @param ex + * the exception which occurred + * @return the entity to be responded containing the exception information + * as entity. + */ + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidationException(final HttpServletRequest request, + final ValidationException ex) { + logRequest(request, ex); + + final ExceptionInfo response = new ExceptionInfo(); + response.setMessage(ex.getMessage()); + response.setExceptionClass(ex.getClass().getName()); + response.setErrorCode(SpServerError.SP_REPO_CONSTRAINT_VIOLATION.getKey()); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/ByteRange.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/ByteRange.java deleted file mode 100644 index 0811ac312..000000000 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/ByteRange.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.rest.util; - -/** - * Byte range for resume download operations. - * - * - * - * - * - */ -public class ByteRange { - public static final String MULTIPART_BOUNDARY = "THIS_STRING_SEPARATES_MULTIPART"; - - private final long start; - private final long end; - private final long length; - private final long total; - - /** - * Construct a byte range. - * - * @param start - * Start of the byte range. - * @param end - * End of the byte range. - * @param total - * Total length of the byte source. - */ - public ByteRange(final long start, final long end, final long total) { - this.start = start; - this.end = end; - length = end - start + 1; - this.total = total; - } - - /** - * @return the start - */ - public long getStart() { - return start; - } - - /** - * @return the end - */ - public long getEnd() { - return end; - } - - /** - * @return the length - */ - public long getLength() { - return length; - } - - /** - * @return the total - */ - public long getTotal() { - return total; - } - - @Override - // NOSONAR - as this is generated - @SuppressWarnings("squid:S864") - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (end ^ end >>> 32); - result = prime * result + (int) (length ^ length >>> 32); - result = prime * result + (int) (start ^ start >>> 32); - result = prime * result + (int) (total ^ total >>> 32); - return result; - } - - @Override - public boolean equals(final Object obj) { // NOSONAR - as this is generated - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ByteRange other = (ByteRange) obj; - if (end != other.end) { - return false; - } - if (length != other.length) { - return false; - } - if (start != other.start) { - return false; - } - if (total != other.total) { - return false; - } - return true; - } - -} diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileSteamingFailedException.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingFailedException.java similarity index 65% rename from hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileSteamingFailedException.java rename to hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingFailedException.java index 826336dcb..6791f5b80 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileSteamingFailedException.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingFailedException.java @@ -14,7 +14,7 @@ import org.eclipse.hawkbit.exception.SpServerError; /** * Thrown if artifact content streaming to client failed. */ -public final class FileSteamingFailedException extends AbstractServerRtException { +public final class FileStreamingFailedException extends AbstractServerRtException { private static final long serialVersionUID = 1L; @@ -22,7 +22,7 @@ public final class FileSteamingFailedException extends AbstractServerRtException * Creates a new FileUploadFailedException with * {@link SpServerError#SP_REST_BODY_NOT_READABLE} error. */ - public FileSteamingFailedException() { + public FileStreamingFailedException() { super(SpServerError.SP_ARTIFACT_LOAD_FAILED); } @@ -32,7 +32,7 @@ public final class FileSteamingFailedException extends AbstractServerRtException * @param cause * for the exception */ - public FileSteamingFailedException(final Throwable cause) { + public FileStreamingFailedException(final Throwable cause) { super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause); } @@ -42,7 +42,19 @@ public final class FileSteamingFailedException extends AbstractServerRtException * @param message * of the error */ - public FileSteamingFailedException(final String message) { + public FileStreamingFailedException(final String message) { super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED); } + + /** + * Constructor with error string and cause. + * + * @param message + * of the error + * @param cause + * for the exception + */ + public FileStreamingFailedException(final String message, final Throwable cause) { + super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED, cause); + } } diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingProgressListener.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingProgressListener.java new file mode 100644 index 000000000..0c43265af --- /dev/null +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingProgressListener.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.util; + +/** + * Listener for progress on artifact file streaming. + * + */ +@FunctionalInterface +public interface FileStreamingProgressListener { + + /** + * Called multiple times during streaming. + * + * @param requestedBytes + * requested bytes of the request + * @param shippedBytesSinceLast + * since the last report + * @param shippedBytesOverall + * during the request + */ + void progress(long requestedBytes, long shippedBytesSinceLast, long shippedBytesOverall); +} diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingUtil.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingUtil.java new file mode 100644 index 000000000..fef8d7b9e --- /dev/null +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/FileStreamingUtil.java @@ -0,0 +1,445 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteStreams; +import com.google.common.math.DoubleMath; + +/** + * Utility class for artifact file streaming. + */ +public final class FileStreamingUtil { + + private static final Logger LOG = LoggerFactory.getLogger(FileStreamingUtil.class); + + /** + * File suffix for MDH hash download (see Linux md5sum). + */ + public static final String ARTIFACT_MD5_DWNL_SUFFIX = ".MD5SUM"; + + private static final int BUFFER_SIZE = 0x2000; // 8k + + private FileStreamingUtil() { + + } + + /** + * Write a md5 file response. + * + * @param response + * the response + * @param md5Hash + * of the artifact + * @param filename + * as provided by the client + * @return the response + * @throws IOException + * cannot write output stream + */ + public static ResponseEntity writeMD5FileResponse(final HttpServletResponse response, final String md5Hash, + final String filename) throws IOException { + + if (md5Hash == null) { + return ResponseEntity.notFound().build(); + } + + final StringBuilder builder = new StringBuilder(); + builder.append(md5Hash); + builder.append(" "); + builder.append(filename); + final byte[] content = builder.toString().getBytes(StandardCharsets.US_ASCII); + + final StringBuilder header = new StringBuilder().append("attachment;filename=").append(filename) + .append(ARTIFACT_MD5_DWNL_SUFFIX); + + response.setContentLength(content.length); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, header.toString()); + + response.getOutputStream().write(content); + + return ResponseEntity.ok().build(); + } + + /** + *

+ * Write response with target relation and publishes events concerning the + * download progress based on given update action status. + *

+ * + *

+ * The request supports RFC7233 range requests. + *

+ * + * @param artifact + * the artifact + * @param filename + * to be written to the client response + * @param lastModified + * unix timestamp of the artifact + * @param response + * to be sent back to the requesting client + * @param request + * from the client + * @param progressListener + * to write progress updates to + * + * @return http response + * + * @see https://tools.ietf.org + * /html/rfc7233 + * + * @throws FileStreamingFailedException + * if streaming fails + */ + public static ResponseEntity writeFileResponse(final AbstractDbArtifact artifact, + final String filename, final Long lastModified, final HttpServletResponse response, + final HttpServletRequest request, final FileStreamingProgressListener progressListener) { + + ResponseEntity result; + + final String etag = artifact.getHashes().getSha1(); + final long length = artifact.getSize(); + + response.reset(); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + filename); + response.setHeader(HttpHeaders.ETAG, etag); + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); + if (lastModified != null) { + response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified); + } + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setBufferSize(BUFFER_SIZE); + + final ByteRange full = new ByteRange(0, length - 1, length); + final List ranges = new ArrayList<>(); + + // Validate and process Range and If-Range headers. + final String range = request.getHeader("Range"); + if (lastModified != null && range != null) { + LOG.debug("range header for filename ({}) is: {}", filename, range); + + // Range header matches"bytes=n-n,n-n,n-n..." + if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); + LOG.debug("range header for filename ({}) is not satisfiable: ", filename); + return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + } + + // RFC: if the representation is unchanged, send me the part(s) that + // I am requesting in + // Range; otherwise, send me the entire representation. + checkForShortcut(request, etag, lastModified, full, ranges); + + // it seems there are valid ranges + result = extractRange(response, length, ranges, range); + // return if range extraction turned out to be invalid + if (result != null) { + return result; + } + } + + // full request - no range + if (ranges.isEmpty() || ranges.get(0).equals(full)) { + LOG.debug("filename ({}) results into a full request: ", filename); + result = handleFullFileRequest(artifact, filename, response, progressListener, full); + } + // standard range request + else if (ranges.size() == 1) { + LOG.debug("filename ({}) results into a standard range request: ", filename); + result = handleStandardRangeRequest(artifact, filename, response, progressListener, ranges); + } + // multipart range request + else { + LOG.debug("filename ({}) results into a multipart range request: ", filename); + result = handleMultipartRangeRequest(artifact, filename, response, progressListener, ranges); + } + + return result; + } + + private static ResponseEntity handleFullFileRequest(final AbstractDbArtifact artifact, + final String filename, final HttpServletResponse response, + final FileStreamingProgressListener progressListener, final ByteRange full) { + final ByteRange r = full; + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); + response.setContentLengthLong(r.getLength()); + + try (InputStream from = artifact.getFileInputStream()) { + final ServletOutputStream to = response.getOutputStream(); + copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename); + } catch (final IOException e) { + throw new FileStreamingFailedException("fullfileRequest " + filename, e); + } + + return ResponseEntity.ok().build(); + } + + private static ResponseEntity extractRange(final HttpServletResponse response, final long length, + final List ranges, final String range) { + + if (ranges.isEmpty()) { + for (final String part : range.substring(6).split(",")) { + long start = sublong(part, 0, part.indexOf('-')); + long end = sublong(part, part.indexOf('-') + 1, part.length()); + + if (start == -1) { + start = length - end; + end = length - 1; + } else if (end == -1 || end > length - 1) { + end = length - 1; + } + + // Check if Range is syntactically valid. If not, then return + // 416. + if (start > end) { + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); + return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + } + + // Add range. + ranges.add(new ByteRange(start, end, length)); + } + } + + return null; + } + + private static long sublong(final String value, final int beginIndex, final int endIndex) { + final String substring = value.substring(beginIndex, endIndex); + return substring.length() > 0 ? Long.parseLong(substring) : -1; + } + + private static void checkForShortcut(final HttpServletRequest request, final String etag, final long lastModified, + final ByteRange full, final List ranges) { + final String ifRange = request.getHeader(HttpHeaders.IF_RANGE); + if (ifRange != null && !ifRange.equals(etag)) { + try { + final long ifRangeTime = request.getDateHeader(HttpHeaders.IF_RANGE); + if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { + ranges.add(full); + } + } catch (final IllegalArgumentException ignore) { + LOG.info("Invalid if-range header field", ignore); + ranges.add(full); + } + } + } + + private static ResponseEntity handleMultipartRangeRequest(final AbstractDbArtifact artifact, + final String filename, final HttpServletResponse response, + final FileStreamingProgressListener progressListener, final List ranges) { + + response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + + try { + final ServletOutputStream to = response.getOutputStream(); + + for (final ByteRange r : ranges) { + try (InputStream from = artifact.getFileInputStream()) { + + // Add multipart boundary and header fields for every range. + to.println(); + to.println("--" + ByteRange.MULTIPART_BOUNDARY); + to.println(HttpHeaders.CONTENT_RANGE + ": bytes " + r.getStart() + "-" + r.getEnd() + "/" + + r.getTotal()); + + // Copy single part range of multi part range. + copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename); + } + } + + // End with final multipart boundary. + to.println(); + to.print("--" + ByteRange.MULTIPART_BOUNDARY + "--"); + } catch (final IOException e) { + throw new FileStreamingFailedException("multipartRangeRequest " + filename, e); + } + + return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build(); + } + + private static ResponseEntity handleStandardRangeRequest(final AbstractDbArtifact artifact, + final String filename, final HttpServletResponse response, + final FileStreamingProgressListener progressListener, final List ranges) { + final ByteRange r = ranges.get(0); + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); + response.setContentLengthLong(r.getLength()); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + + try (InputStream from = artifact.getFileInputStream()) { + final ServletOutputStream to = response.getOutputStream(); + copyStreams(from, to, progressListener, r.getStart(), r.getLength(), filename); + } catch (final IOException e) { + LOG.error("standardRangeRequest of file ({}) failed!", filename, e); + throw new FileStreamingFailedException(filename); + } + + return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build(); + } + + private static long copyStreams(final InputStream from, final OutputStream to, + final FileStreamingProgressListener progressListener, final long start, final long length, + final String filename) throws IOException { + + final long startMillis = System.currentTimeMillis(); + LOG.trace("Start of copy-streams of file {} from {} to {}", filename, start, length); + + Preconditions.checkNotNull(from); + Preconditions.checkNotNull(to); + final byte[] buf = new byte[BUFFER_SIZE]; + long total = 0; + int progressPercent = 1; + + ByteStreams.skipFully(from, start); + + long toRead = length; + boolean toContinue = true; + long shippedSinceLastEvent = 0; + + while (toContinue) { + final int r = from.read(buf); + if (r == -1) { + break; + } + + toRead -= r; + if (toRead > 0) { + to.write(buf, 0, r); + total += r; + shippedSinceLastEvent += r; + } else { + to.write(buf, 0, (int) toRead + r); + total += toRead + r; + shippedSinceLastEvent += toRead + r; + toContinue = false; + } + + if (progressListener != null) { + final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, RoundingMode.DOWN); + + // every 10 percent an event + if (newPercent == 100 || newPercent > progressPercent + 10) { + progressPercent = newPercent; + progressListener.progress(length, shippedSinceLastEvent, total); + shippedSinceLastEvent = 0; + } + } + } + + final long totalTime = System.currentTimeMillis() - startMillis; + + if (total < length) { + throw new FileStreamingFailedException(filename + ": " + (length - total) + + " bytes could not be written to client, total time on write: !" + totalTime + " ms"); + } + + LOG.trace("Finished copy-stream of file {} with length {} in {} ms", filename, length, totalTime); + + return total; + } + + private static final class ByteRange { + private static final String MULTIPART_BOUNDARY = "THIS_STRING_SEPARATES_MULTIPART"; + + private final long start; + private final long end; + private final long length; + private final long total; + + private ByteRange(final long start, final long end, final long total) { + this.start = start; + this.end = end; + length = end - start + 1; + this.total = total; + } + + private long getStart() { + return start; + } + + private long getEnd() { + return end; + } + + private long getLength() { + return length; + } + + private long getTotal() { + return total; + } + + @Override + // Generated code + @SuppressWarnings("squid:S864") + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (end ^ (end >>> 32)); + result = prime * result + (int) (length ^ (length >>> 32)); + result = prime * result + (int) (start ^ (start >>> 32)); + result = prime * result + (int) (total ^ (total >>> 32)); + return result; + } + + @Override + // Generated code + @SuppressWarnings("squid:S1126") + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ByteRange other = (ByteRange) obj; + if (end != other.end) { + return false; + } + if (length != other.length) { + return false; + } + if (start != other.start) { + return false; + } + if (total != other.total) { + return false; + } + return true; + } + + } + +} diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/HttpUtil.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/HttpUtil.java new file mode 100644 index 000000000..87f2bdd7d --- /dev/null +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/HttpUtil.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.util; + +import java.util.Arrays; + +/** + * Utility class for the Rest Source API. + */ +public final class HttpUtil { + + private HttpUtil() { + + } + + /** + * Checks given CSV string for defined match value or wildcard. + * + * @param matchHeader + * to search through + * @param toMatch + * to search for + * + * @return true if string matches. + */ + public static boolean matchesHttpHeader(final String matchHeader, final String toMatch) { + final String[] matchValues = matchHeader.split("\\s*,\\s*"); + Arrays.sort(matchValues); + return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; + } + +} diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java deleted file mode 100644 index 421359115..000000000 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java +++ /dev/null @@ -1,352 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.rest.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; -import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.model.ActionStatus; -import org.eclipse.hawkbit.repository.model.Artifact; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; - -import com.google.common.base.Preconditions; -import com.google.common.math.DoubleMath; -import com.google.common.net.HttpHeaders; - -/** - * Utility class for the Rest Source API. - */ -public final class RestResourceConversionHelper { - - private static final Logger LOG = LoggerFactory.getLogger(RestResourceConversionHelper.class); - - private static final int BUFFER_SIZE = 4096; - - private RestResourceConversionHelper() { - - } - - /** - * Write response without target relation. - * - * @param artifact - * the artifact - * @param servletResponse - * to be sent back to the requesting client - * @param request - * from the client - * @param file - * to be write to the client response - * - * @return http code - */ - public static ResponseEntity writeFileResponse(final Artifact artifact, - final HttpServletResponse servletResponse, final HttpServletRequest request, final DbArtifact file) { - return writeFileResponse(artifact, servletResponse, request, file, null, null); - } - - /** - *

- * Write response with target relation and publishes events concerning the - * download progress based on given update action status. - *

- * - *

- * The request supports RFC7233 range requests. - *

- * - * @param artifact - * the artifact - * @param response - * to be sent back to the requesting client - * @param request - * from the client - * @param file - * to be write to the client response - * @param controllerManagement - * to write progress updates to - * @param statusId - * of the {@link ActionStatus} - * - * @return http code - * - * @see https://tools.ietf.org - * /html/rfc7233 - */ - public static ResponseEntity writeFileResponse(final Artifact artifact, - final HttpServletResponse response, final HttpServletRequest request, final DbArtifact file, - final ControllerManagement controllerManagement, final Long statusId) { - - ResponseEntity result; - - final String etag = artifact.getSha1Hash(); - final Long lastModified = artifact.getLastModifiedAt() != null ? artifact.getLastModifiedAt() - : artifact.getCreatedAt(); - final long length = file.getSize(); - - response.reset(); - response.setBufferSize(BUFFER_SIZE); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + artifact.getFilename()); - response.setHeader(HttpHeaders.ETAG, etag); - response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); - response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - - final ByteRange full = new ByteRange(0, length - 1, length); - final List ranges = new ArrayList<>(); - - // Validate and process Range and If-Range headers. - final String range = request.getHeader("Range"); - if (range != null) { - LOG.debug("range header for filename ({}) is: {}", artifact.getFilename(), range); - - // Range header matches"bytes=n-n,n-n,n-n..." - if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); - LOG.debug("range header for filename ({}) is not satisfiable: ", artifact.getFilename()); - return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); - } - - // RFC: if the representation is unchanged, send me the part(s) that - // I am requesting in - // Range; otherwise, send me the entire representation. - checkForShortcut(request, etag, lastModified, full, ranges); - - // it seems there are valid ranges - result = extractRange(response, length, ranges, range); - // return if range extraction turned out to be invalid - if (result != null) { - return result; - } - } - - // full request - no range - if (ranges.isEmpty() || ranges.get(0).equals(full)) { - LOG.debug("filename ({}) results into a full request: ", artifact.getFilename()); - handleFullFileRequest(artifact, response, file, controllerManagement, statusId, full); - result = ResponseEntity.ok().build(); - } - // standard range request - else if (ranges.size() == 1) { - LOG.debug("filename ({}) results into a standard range request: ", artifact.getFilename()); - handleStandardRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); - result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT); - } - // multipart range request - else { - LOG.debug("filename ({}) results into a multipart range request: ", artifact.getFilename()); - handleMultipartRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); - result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT); - } - - return result; - } - - private static void handleFullFileRequest(final Artifact artifact, final HttpServletResponse response, - final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, - final ByteRange full) { - final ByteRange r = full; - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); - response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength())); - - try (InputStream inputStream = file.getFileInputStream()) { - copyStreams(inputStream, response.getOutputStream(), controllerManagement, statusId, r.getStart(), - r.getLength()); - } catch (final IOException e) { - LOG.error("fullfileRequest of file ({}) failed!", artifact.getFilename(), e); - throw new FileSteamingFailedException(artifact.getFilename()); - } - } - - private static ResponseEntity extractRange(final HttpServletResponse response, final long length, - final List ranges, final String range) { - - if (ranges.isEmpty()) { - for (final String part : range.substring(6).split(",")) { - long start = sublong(part, 0, part.indexOf('-')); - long end = sublong(part, part.indexOf('-') + 1, part.length()); - - if (start == -1) { - start = length - end; - end = length - 1; - } else if (end == -1 || end > length - 1) { - end = length - 1; - } - - // Check if Range is syntactically valid. If not, then return - // 416. - if (start > end) { - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); - return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); - } - - // Add range. - ranges.add(new ByteRange(start, end, length)); - } - } - - return null; - } - - private static long sublong(final String value, final int beginIndex, final int endIndex) { - final String substring = value.substring(beginIndex, endIndex); - return substring.length() > 0 ? Long.parseLong(substring) : -1; - } - - private static void checkForShortcut(final HttpServletRequest request, final String etag, final long lastModified, - final ByteRange full, final List ranges) { - final String ifRange = request.getHeader(HttpHeaders.IF_RANGE); - if (ifRange != null && !ifRange.equals(etag)) { - try { - final long ifRangeTime = request.getDateHeader(HttpHeaders.IF_RANGE); - if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { - ranges.add(full); - } - } catch (final IllegalArgumentException ignore) { - LOG.info("Invalid if-range header field", ignore); - ranges.add(full); - } - } - } - - private static void handleMultipartRangeRequest(final Artifact artifact, final HttpServletResponse response, - final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, - final List ranges) { - response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY); - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - - for (final ByteRange r : ranges) { - try (InputStream inputStream = file.getFileInputStream()) { - - // Add multipart boundary and header fields for every range. - response.getOutputStream().println(); - response.getOutputStream().println("--" + ByteRange.MULTIPART_BOUNDARY); - response.getOutputStream() - .println("Content-Range: bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); - - // Copy single part range of multi part range. - copyStreams(inputStream, response.getOutputStream(), controllerManagement, statusId, r.getStart(), - r.getLength()); - } catch (final IOException e) { - throwFileStreamingFailedException(artifact, e); - } - } - try { - // End with final multipart boundary. - response.getOutputStream().println(); - response.getOutputStream().print("--" + ByteRange.MULTIPART_BOUNDARY + "--"); - } catch (final IOException e) { - throwFileStreamingFailedException(artifact, e); - } - } - - private static void throwFileStreamingFailedException(final Artifact artifact, final IOException e) { - LOG.error("multipartRangeRequest of file ({}) failed!", artifact.getFilename(), e); - throw new FileSteamingFailedException(artifact.getFilename()); - } - - private static void handleStandardRangeRequest(final Artifact artifact, final HttpServletResponse response, - final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, - final List ranges) { - final ByteRange r = ranges.get(0); - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); - response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength())); - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - - try (InputStream inputStream = file.getFileInputStream()) { - copyStreams(inputStream, response.getOutputStream(), controllerManagement, statusId, r.getStart(), - r.getLength()); - } catch (final IOException e) { - LOG.error("standardRangeRequest of file ({}) failed!", artifact.getFilename(), e); - throw new FileSteamingFailedException(artifact.getFilename()); - } - } - - private static long copyStreams(final InputStream from, final OutputStream to, - final ControllerManagement controllerManagement, final Long statusId, final long start, final long length) - throws IOException { - Preconditions.checkNotNull(from); - Preconditions.checkNotNull(to); - final byte[] buf = new byte[BUFFER_SIZE]; - long total = 0; - int progressPercent = 1; - - // skipp until start is reached - long skipped = 0; - do { - skipped += from.skip(start); - } while (skipped < start); - - long toRead = length; - boolean toContinue = true; - long shippedSinceLastEvent = 0; - - while (toContinue) { - final int r = from.read(buf); - if (r == -1) { - break; - } - - toRead -= r; - if (toRead > 0) { - to.write(buf, 0, r); - total += r; - shippedSinceLastEvent += r; - } else { - to.write(buf, 0, (int) toRead + r); - total += toRead + r; - shippedSinceLastEvent += toRead + r; - toContinue = false; - } - - if (controllerManagement != null) { - final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, RoundingMode.DOWN); - - // every 10 percent an event - if (newPercent == 100 || newPercent > progressPercent + 10) { - progressPercent = newPercent; - controllerManagement.downloadProgress(statusId, length, shippedSinceLastEvent, total); - shippedSinceLastEvent = 0; - } - } - } - return total; - } - - /** - * Checks given CSV string for defined match value or * wildcard. - * - * @param matchHeader - * to search through - * @param toMatch - * to search for - * - * @return true if string matches. - */ - public static boolean matchesHttpHeader(final String matchHeader, final String toMatch) { - final String[] matchValues = matchHeader.split("\\s*,\\s*"); - Arrays.sort(matchValues); - return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; - } - -} diff --git a/hawkbit-runtime/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/StreamAwareErrorController.java b/hawkbit-runtime/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/StreamAwareErrorController.java index e6bc8590d..76c76373e 100644 --- a/hawkbit-runtime/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/StreamAwareErrorController.java +++ b/hawkbit-runtime/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/StreamAwareErrorController.java @@ -41,7 +41,7 @@ public class StreamAwareErrorController extends BasicErrorController { } @RequestMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - ResponseEntity errorStream(final HttpServletRequest request, final HttpServletResponse response) { + public ResponseEntity errorStream(final HttpServletRequest request, final HttpServletResponse response) { final HttpStatus status = getStatus(request); return new ResponseEntity<>(status); }