@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact;
|
||||
package org.eclipse.hawkbit.artifact;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
@@ -22,17 +22,17 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HexFormat;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.artifact.exception.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
|
||||
import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Abstract utility class for ArtifactRepository implementations with common functionality, e.g. computation of hashes.
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
public abstract class AbstractArtifactStorage implements ArtifactStorage {
|
||||
|
||||
private static final String TEMP_FILE_PREFIX = "tmp";
|
||||
private static final String TEMP_FILE_SUFFIX = "artifactrepo";
|
||||
@@ -40,9 +40,9 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 is not used security related
|
||||
@SuppressWarnings("squid:S2070")
|
||||
@Override
|
||||
public AbstractDbArtifact store(
|
||||
public StoredArtifactInfo store(
|
||||
final String tenant, final InputStream content, final String filename, final String contentType,
|
||||
final DbArtifactHash providedHashes) {
|
||||
final ArtifactHashes providedHashes) {
|
||||
final MessageDigest mdSHA1;
|
||||
final MessageDigest mdMD5;
|
||||
final MessageDigest mdSHA256;
|
||||
@@ -60,19 +60,19 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
|
||||
final HexFormat hexFormat = HexFormat.of().withLowerCase();
|
||||
|
||||
final String sha1Hash16 = hexFormat.formatHex(mdSHA1.digest());
|
||||
final String md5Hash16 = hexFormat.formatHex(mdMD5.digest());
|
||||
final String sha256Hash16 = hexFormat.formatHex(mdSHA256.digest());
|
||||
final String sha1Hash = hexFormat.formatHex(mdSHA1.digest());
|
||||
final String md5Hash = hexFormat.formatHex(mdMD5.digest());
|
||||
final String sha256Hash = hexFormat.formatHex(mdSHA256.digest());
|
||||
|
||||
checkHashes(providedHashes, sha1Hash16, md5Hash16, sha256Hash16);
|
||||
checkHashes(providedHashes, sha1Hash, md5Hash, sha256Hash);
|
||||
|
||||
// Check if file with same sha1 hash exists and if so return it
|
||||
if (existsBySha1(tenant, sha1Hash16)) {
|
||||
if (existsBySha1(tenant, sha1Hash)) {
|
||||
// TODO - shall check if the file is really the same as bytes or just sha1 hash is the same
|
||||
return addMissingHashes(getBySha1(tenant, sha1Hash16), sha1Hash16, md5Hash16, sha256Hash16);
|
||||
return new StoredArtifactInfo(contentType, tempFile.length(), new ArtifactHashes(sha1Hash, md5Hash, sha256Hash));
|
||||
}
|
||||
|
||||
return store(sanitizeTenant(tenant), new DbArtifactHash(sha1Hash16, md5Hash16, sha256Hash16), contentType, tempFile);
|
||||
return store(sanitizeTenant(tenant), new ArtifactHashes(sha1Hash, md5Hash, sha256Hash), contentType, tempFile);
|
||||
} catch (final IOException e) {
|
||||
throw new ArtifactStoreException(e.getMessage(), e);
|
||||
} finally {
|
||||
@@ -104,13 +104,13 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
return file.getPath();
|
||||
}
|
||||
|
||||
protected abstract AbstractDbArtifact store(final String tenant, final DbArtifactHash base16Hashes,
|
||||
final String contentType, final String tempFile) throws IOException;
|
||||
protected abstract StoredArtifactInfo store(
|
||||
final String tenant, final ArtifactHashes base16Hashes, final String contentType, final String tempFile) throws IOException;
|
||||
|
||||
// java:S1066 - more readable with separate "if" statements
|
||||
// java:S4042 - delete reason is not needed
|
||||
@SuppressWarnings({ "java:S1066", "java:S4042" })
|
||||
static File createTempFile(final boolean directory) {
|
||||
public static File createTempFile(final boolean directory) {
|
||||
try {
|
||||
final File file = (directory
|
||||
? Files.createTempDirectory(TEMP_FILE_PREFIX)
|
||||
@@ -138,22 +138,22 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkHashes(final DbArtifactHash providedHashes,
|
||||
final String sha1Hash16, final String md5Hash16, final String sha256Hash16) {
|
||||
private static void checkHashes(
|
||||
final ArtifactHashes providedHashes, final String sha1Hash16, final String md5Hash16, final String sha256Hash16) {
|
||||
if (providedHashes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (areHashesNotMatching(providedHashes.getSha1(), sha1Hash16)) {
|
||||
throw new HashNotMatchException("The given sha1 hash " + providedHashes.getSha1() +
|
||||
if (areHashesNotMatching(providedHashes.sha1(), sha1Hash16)) {
|
||||
throw new HashNotMatchException("The given sha1 hash " + providedHashes.sha1() +
|
||||
" does not match the calculated sha1 hash " + sha1Hash16, HashNotMatchException.SHA1);
|
||||
}
|
||||
if (areHashesNotMatching(providedHashes.getMd5(), md5Hash16)) {
|
||||
throw new HashNotMatchException("The given md5 hash " + providedHashes.getMd5() +
|
||||
if (areHashesNotMatching(providedHashes.md5(), md5Hash16)) {
|
||||
throw new HashNotMatchException("The given md5 hash " + providedHashes.md5() +
|
||||
" does not match the calculated md5 hash " + md5Hash16, HashNotMatchException.MD5);
|
||||
}
|
||||
if (areHashesNotMatching(providedHashes.getSha256(), sha256Hash16)) {
|
||||
throw new HashNotMatchException("The given sha256 hash " + providedHashes.getSha256() +
|
||||
if (areHashesNotMatching(providedHashes.sha256(), sha256Hash16)) {
|
||||
throw new HashNotMatchException("The given sha256 hash " + providedHashes.sha256() +
|
||||
" does not match the calculated sha256 hash " + sha256Hash16, HashNotMatchException.SHA256);
|
||||
}
|
||||
}
|
||||
@@ -167,16 +167,6 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
|
||||
return new DigestInputStream(new DigestInputStream(new DigestInputStream(input, mdSHA256), mdMD5), mdSHA1);
|
||||
}
|
||||
|
||||
private AbstractDbArtifact addMissingHashes(final AbstractDbArtifact existing,
|
||||
final String calculatedSha1, final String calculatedMd5, final String calculatedSha256) {
|
||||
final String sha1 = checkEmpty(existing.getHashes().getSha1(), calculatedSha1);
|
||||
final String md5 = checkEmpty(existing.getHashes().getMd5(), calculatedMd5);
|
||||
final String sha256 = checkEmpty(existing.getHashes().getSha256(), calculatedSha256);
|
||||
|
||||
existing.setHashes(new DbArtifactHash(sha1, md5, sha256));
|
||||
return existing;
|
||||
}
|
||||
|
||||
private String checkEmpty(final String value, final String fallback) {
|
||||
return ObjectUtils.isEmpty(value) ? fallback : value;
|
||||
}
|
||||
@@ -7,22 +7,23 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact;
|
||||
package org.eclipse.hawkbit.artifact;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
|
||||
import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.artifact.exception.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
|
||||
import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
|
||||
|
||||
/**
|
||||
* ArtifactRepository service interface.
|
||||
* Artifact Store service interface.
|
||||
*/
|
||||
public interface ArtifactRepository {
|
||||
public interface ArtifactStorage {
|
||||
|
||||
/**
|
||||
* Stores an artifact into the repository.
|
||||
@@ -37,20 +38,20 @@ public interface ArtifactRepository {
|
||||
* @throws ArtifactStoreException in case storing of the artifact was not successful
|
||||
* @throws HashNotMatchException in case {@code hash} is provided and not matching to the calculated hashes during storing
|
||||
*/
|
||||
AbstractDbArtifact store(
|
||||
StoredArtifactInfo store(
|
||||
@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
|
||||
String contentType, DbArtifactHash hash);
|
||||
String contentType, ArtifactHashes hash);
|
||||
|
||||
/**
|
||||
* Retrieves a {@link AbstractDbArtifact} from the store by its SHA1 hash. Throws {@link org.eclipse.hawkbit.repository.artifact.exception.ArtifactBinaryNotFoundException} if not found.
|
||||
* Retrieves a {@link StoredArtifactInfo} from the store by its SHA1 hash. Throws {@link ArtifactBinaryNotFoundException} if not found.
|
||||
* The caller is responsible to close the InputStream.
|
||||
*
|
||||
* @param tenant the tenant to store the artifact
|
||||
* @param sha1Hash the sha1-hash of the file to lookup.
|
||||
* @return The artifact file object or {@code null} if no file exists.
|
||||
* @throws UnsupportedOperationException if implementation does not support the operation
|
||||
*/
|
||||
AbstractDbArtifact getBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
|
||||
|
||||
InputStream getBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
|
||||
|
||||
/**
|
||||
* Checks if an artifact exists for a given tenant by its sha1 hash
|
||||
@@ -7,13 +7,13 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.encryption;
|
||||
package org.eclipse.hawkbit.artifact.encryption;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionFailedException;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionFailedException;
|
||||
|
||||
/**
|
||||
* Interface definition for artifact encryption.
|
||||
@@ -23,7 +23,7 @@ public interface ArtifactEncryption {
|
||||
/**
|
||||
* Defines the required secret keys for particular encryption algorithm.
|
||||
*
|
||||
* @return list of required secret keys
|
||||
* @return set of required secret keys
|
||||
*/
|
||||
Set<String> requiredSecretKeys();
|
||||
|
||||
@@ -61,4 +61,4 @@ public interface ArtifactEncryption {
|
||||
* @return encryption overhead in byte
|
||||
*/
|
||||
int encryptionSizeOverhead();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.encryption;
|
||||
package org.eclipse.hawkbit.artifact.encryption;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.Optional;
|
||||
* Interface definition for artifact encryption secrets store. It maintains secret key/value pairs
|
||||
* identified by id (e.g. software module id)
|
||||
*/
|
||||
public interface ArtifactEncryptionSecretsStore {
|
||||
public interface ArtifactEncryptionSecretsStorage {
|
||||
|
||||
/**
|
||||
* Adds secret key/value pair associated with particular id (e.g. software module id) to the store.
|
||||
@@ -26,14 +26,6 @@ public interface ArtifactEncryptionSecretsStore {
|
||||
*/
|
||||
void addSecret(final long id, final String secretKey, final String secretValue);
|
||||
|
||||
/**
|
||||
* Checks if secret is present for particular id and key in the store.
|
||||
*
|
||||
* @param id id of the secret
|
||||
* @param secretKey key of the secret
|
||||
*/
|
||||
boolean secretExists(final long id, final String secretKey);
|
||||
|
||||
/**
|
||||
* Retrieves secret value associated with particular id and key from the store.
|
||||
*
|
||||
@@ -41,12 +33,4 @@ public interface ArtifactEncryptionSecretsStore {
|
||||
* @param secretKey key of the secret
|
||||
*/
|
||||
Optional<String> getSecret(final long id, final String secretKey);
|
||||
|
||||
/**
|
||||
* Removes secret key/value pair associated with particular id from the store.
|
||||
*
|
||||
* @param id id of the secret
|
||||
* @param secretKey key of the secret
|
||||
*/
|
||||
void removeSecret(final long id, final String secretKey);
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.encryption;
|
||||
package org.eclipse.hawkbit.artifact.encryption;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
@@ -17,12 +17,11 @@ import java.util.Set;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Service responsible for encryption operations. Should be registered as a bean in order its autowired dependencies
|
||||
* to be injected.
|
||||
* Service responsible for encryption operations. Should be registered as a bean in order its autowired dependencies to be injected.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings("java:S6548") // singleton holder ensures static access to spring resources in some places
|
||||
@@ -31,7 +30,7 @@ public final class ArtifactEncryptionService {
|
||||
private static final ArtifactEncryptionService SINGLETON = new ArtifactEncryptionService();
|
||||
|
||||
private ArtifactEncryption artifactEncryption;
|
||||
private ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore;
|
||||
private ArtifactEncryptionSecretsStorage artifactEncryptionSecretsStore;
|
||||
|
||||
/**
|
||||
* @return the artifact encryption service singleton instance
|
||||
@@ -46,7 +45,7 @@ public final class ArtifactEncryptionService {
|
||||
}
|
||||
|
||||
@Autowired(required = false) // spring setter injection
|
||||
public void setArtifactEncryptionSecretsStore(final ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore) {
|
||||
public void setArtifactEncryptionSecretsStore(final ArtifactEncryptionSecretsStorage artifactEncryptionSecretsStore) {
|
||||
this.artifactEncryptionSecretsStore = artifactEncryptionSecretsStore;
|
||||
}
|
||||
|
||||
@@ -111,7 +110,7 @@ public final class ArtifactEncryptionService {
|
||||
throw new ArtifactEncryptionUnsupportedException("Artifact decryption is not supported.");
|
||||
}
|
||||
|
||||
final var secrets = getEncryptionSecrets(id);
|
||||
final Map<String, String> secrets = getEncryptionSecrets(id);
|
||||
try {
|
||||
return artifactEncryption.decryptStream(secrets, encryptedArtifactStream);
|
||||
} finally {
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -28,29 +28,7 @@ public class ArtifactBinaryNoLongerExistsException extends AbstractServerRtExcep
|
||||
|
||||
private static final SpServerError THIS_ERROR = SpServerError.SP_ARTIFACT_BINARY_DELETED;
|
||||
|
||||
/**
|
||||
* Creates a new ArtifactBinaryGoneException error.
|
||||
*/
|
||||
public ArtifactBinaryNoLongerExistsException() {
|
||||
super(THIS_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ArtifactBinaryGoneException error with cause.
|
||||
*
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public ArtifactBinaryNoLongerExistsException(final Throwable cause) {
|
||||
super(THIS_ERROR, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ArtifactBinaryGoneException error with message.
|
||||
*
|
||||
* @param message of the error
|
||||
*/
|
||||
public ArtifactBinaryNoLongerExistsException(final String message) {
|
||||
super(message, THIS_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -23,25 +23,7 @@ public final class ArtifactBinaryNotFoundException extends AbstractServerRtExcep
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new FileUploadFailedException with
|
||||
* {@link SpServerError#SP_ARTIFACT_LOAD_FAILED} error.
|
||||
*/
|
||||
public ArtifactBinaryNotFoundException() {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public ArtifactBinaryNotFoundException(final Throwable cause) {
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message of the error
|
||||
*/
|
||||
public ArtifactBinaryNotFoundException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED);
|
||||
super(SpServerError.SP_ARTIFACT_LOAD_FAILED, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -26,25 +26,7 @@ public final class ArtifactDeleteFailedException extends AbstractServerRtExcepti
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new FileUploadFailedException with
|
||||
* {@link SpServerError#SP_ARTIFACT_DELETE_FAILED} error.
|
||||
*/
|
||||
public ArtifactDeleteFailedException() {
|
||||
super(SpServerError.SP_ARTIFACT_DELETE_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public ArtifactDeleteFailedException(final Throwable cause) {
|
||||
super(SpServerError.SP_ARTIFACT_DELETE_FAILED, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message of the error
|
||||
*/
|
||||
public ArtifactDeleteFailedException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_DELETE_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -35,16 +35,8 @@ public final class ArtifactEncryptionFailedException extends AbstractServerRtExc
|
||||
@Getter
|
||||
private final EncryptionOperation encryptionOperation;
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation) {
|
||||
this(encryptionOperation, null, null);
|
||||
}
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message) {
|
||||
this(encryptionOperation, message, null);
|
||||
}
|
||||
|
||||
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message, final Throwable cause) {
|
||||
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, cause);
|
||||
super(SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, message, cause);
|
||||
this.encryptionOperation = encryptionOperation;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -22,17 +22,7 @@ public final class ArtifactEncryptionUnsupportedException extends AbstractServer
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public ArtifactEncryptionUnsupportedException() {
|
||||
super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message of the error
|
||||
*/
|
||||
public ArtifactEncryptionUnsupportedException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
|
||||
super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -19,12 +19,10 @@ public class ArtifactStoreException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a ArtifactStoreException with message and cause.
|
||||
*
|
||||
* @param message the message of the exception
|
||||
* @param cause of the exception
|
||||
*/
|
||||
public ArtifactStoreException(final String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public ArtifactStoreException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -23,34 +23,7 @@ public final class ArtifactUploadFailedException extends AbstractServerRtExcepti
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new FileUploadFailedException with
|
||||
* {@link SpServerError#SP_ARTIFACT_UPLOAD_FAILED} error.
|
||||
*/
|
||||
public ArtifactUploadFailedException() {
|
||||
super(SpServerError.SP_ARTIFACT_UPLOAD_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause for the exception
|
||||
*/
|
||||
public ArtifactUploadFailedException(final Throwable cause) {
|
||||
super(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message of the error
|
||||
*/
|
||||
public ArtifactUploadFailedException(final String message) {
|
||||
super(message, SpServerError.SP_ARTIFACT_UPLOAD_FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message for the error
|
||||
* @param cause of the error
|
||||
*/
|
||||
public ArtifactUploadFailedException(final String message, final Throwable cause) {
|
||||
super(message, SpServerError.SP_ARTIFACT_UPLOAD_FAILED, cause);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Thrown if file size quota is exceeded
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class FileSizeQuotaExceededException extends AbstractServerRtException {
|
||||
|
||||
private static final String MAX_ARTIFACT_SIZE_EXCEEDED = "Maximum artifact size (%s) exceeded.";
|
||||
private static final SpServerError errorType = SpServerError.SP_FILE_SIZE_QUOTA_EXCEEDED;
|
||||
|
||||
private static final String KB = "KB";
|
||||
private static final String MB = "MB";
|
||||
|
||||
private final long exceededQuotaValue;
|
||||
|
||||
public FileSizeQuotaExceededException(final long exceededQuotaValue) {
|
||||
super(errorType, createQuotaErrorMessage(exceededQuotaValue));
|
||||
this.exceededQuotaValue = exceededQuotaValue;
|
||||
}
|
||||
|
||||
private static String createQuotaErrorMessage(final long exceededQuotaValue) {
|
||||
return String.format(MAX_ARTIFACT_SIZE_EXCEEDED, byteValueToReadableString(exceededQuotaValue));
|
||||
}
|
||||
|
||||
static String byteValueToReadableString(final long byteValue) {
|
||||
double outputValue = byteValue / 1024.0;
|
||||
String unit = KB;
|
||||
if (outputValue >= 1024) {
|
||||
outputValue = outputValue / 1024.0;
|
||||
unit = MB;
|
||||
}
|
||||
// We cut decimal places to avoid localization handling
|
||||
return (long) outputValue + " " + unit;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.exception;
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -28,12 +28,6 @@ public class HashNotMatchException extends RuntimeException {
|
||||
|
||||
private final String hashFunction;
|
||||
|
||||
/**
|
||||
* Constructs a HashNotMatchException with message.
|
||||
*
|
||||
* @param message the message of the exception
|
||||
* @param hashFunction the hash function which caused this exception
|
||||
*/
|
||||
public HashNotMatchException(final String message, final String hashFunction) {
|
||||
super(message);
|
||||
this.hashFunction = hashFunction;
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.exception;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
|
||||
/**
|
||||
* Thrown if storage quota is exceeded
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class StorageQuotaExceededException extends AbstractServerRtException {
|
||||
|
||||
private static final String MAX_ARTIFACT_SIZE_TOTAL_EXCEEDED = "Storage quota exceeded, %s left.";
|
||||
private static final SpServerError errorType = SpServerError.SP_STORAGE_QUOTA_EXCEEDED;
|
||||
|
||||
private final long exceededQuotaValue;
|
||||
|
||||
public StorageQuotaExceededException(final long exceededQuotaValue) {
|
||||
super(errorType, createQuotaErrorMessage(exceededQuotaValue));
|
||||
this.exceededQuotaValue = exceededQuotaValue;
|
||||
}
|
||||
|
||||
private static String createQuotaErrorMessage(final long exceededQuotaValue) {
|
||||
return String.format(MAX_ARTIFACT_SIZE_TOTAL_EXCEEDED, FileSizeQuotaExceededException.byteValueToReadableString(exceededQuotaValue));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.model;
|
||||
|
||||
/**
|
||||
* Representation of artifact hashes.
|
||||
*/
|
||||
public record ArtifactHashes(String sha1, String md5, String sha256) {}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ToString
|
||||
public class ArtifactStream extends InputStream {
|
||||
|
||||
@EqualsAndHashCode.Exclude
|
||||
@ToString.Exclude
|
||||
private final InputStream inputStream;
|
||||
@Getter
|
||||
private final long size;
|
||||
@Getter
|
||||
private final String sha1Hash;
|
||||
|
||||
public ArtifactStream(InputStream inputStream, long size, String sha1Hash) {
|
||||
this.inputStream = inputStream;
|
||||
this.size = size;
|
||||
this.sha1Hash = sha1Hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
return inputStream.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
@@ -7,28 +7,25 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.model;
|
||||
package org.eclipse.hawkbit.artifact.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Database representation of artifact.
|
||||
* Info for an imported artifact binary.
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractDbArtifact implements DbArtifact {
|
||||
public class StoredArtifactInfo {
|
||||
|
||||
private final String artifactId;
|
||||
private final long size;
|
||||
private final String contentType;
|
||||
private final long size;
|
||||
private final ArtifactHashes hashes;
|
||||
|
||||
private DbArtifactHash hashes;
|
||||
|
||||
protected AbstractDbArtifact(final String artifactId, final DbArtifactHash hashes, final long size, final String contentType) {
|
||||
this.artifactId = Objects.requireNonNull(artifactId, "Artifact ID cannot be null");
|
||||
public StoredArtifactInfo(final String contentType, final long size, final ArtifactHashes hashes) {
|
||||
this.hashes = Objects.requireNonNull(hashes, "Hashes cannot be null");
|
||||
this.size = size;
|
||||
this.contentType = contentType;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.urlresolver;
|
||||
|
||||
/**
|
||||
* Container for a generated Artifact URL.
|
||||
*/
|
||||
public record ArtifactUrl(String protocol, String rel, String ref) {}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.urlresolver;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface declaration of the {@link ArtifactUrlResolver} which generates the URLs to specific artifacts.
|
||||
*/
|
||||
public interface ArtifactUrlResolver {
|
||||
|
||||
/**
|
||||
* hawkBit API type.
|
||||
*/
|
||||
enum ApiType {
|
||||
|
||||
/**
|
||||
* Support for Device Management Federation API.
|
||||
*/
|
||||
DMF,
|
||||
|
||||
/**
|
||||
* Support for Direct Device Integration API.
|
||||
*/
|
||||
DDI,
|
||||
|
||||
/**
|
||||
* Support for Management API.
|
||||
*/
|
||||
MGMT
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generated download URL for a given artifact parameters for a specific protocol.
|
||||
*
|
||||
* @param downloadDescriptor data for URL generation
|
||||
* @param api given protocol that URL needs to support
|
||||
* @return a URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(DownloadDescriptor downloadDescriptor, ApiType api);
|
||||
|
||||
/**
|
||||
* Returns a generated download URL for a given artifact parameters for a specific protocol.
|
||||
*
|
||||
* @param downloadDescriptor data for URL generation
|
||||
* @param api given protocol that URL needs to support
|
||||
* @param requestUri of the request that allows the handler to align the generated URL to the original request.
|
||||
* @return a URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(DownloadDescriptor downloadDescriptor, ApiType api, URI requestUri);
|
||||
|
||||
/**
|
||||
* Container for variables available to the {@link ArtifactUrlResolver}.
|
||||
*/
|
||||
record DownloadDescriptor(String tenant, String controllerId, Long softwareModuleId, String filename, String sha1) {}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.artifact.urlresolver;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties.UrlProtocol;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation for ArtifactUrlHandler for creating urls to download resource based on patterns configured by
|
||||
* {@link PropertyBasedArtifactUrlResolverProperties}.
|
||||
* <p/>
|
||||
* This mechanism can be used to generate links to arbitrary file hosting infrastructure. However, the hawkBit update server
|
||||
* supports hosting files as well in the following {@link UrlProtocol#getRef()} patterns:
|
||||
* <p/>
|
||||
* Default: </br>
|
||||
* {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}
|
||||
* /softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
|
||||
* <p/>
|
||||
* Default (MD5SUM files):
|
||||
* {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}/
|
||||
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
|
||||
*/
|
||||
public class PropertyBasedArtifactUrlResolver implements ArtifactUrlResolver {
|
||||
|
||||
private static final String PROTOCOL_PLACEHOLDER = "protocol";
|
||||
private static final String PROTOCOL_REQUEST_PLACEHOLDER = "protocolRequest";
|
||||
private static final String HOSTNAME_PLACEHOLDER = "hostname";
|
||||
private static final String HOSTNAME_REQUEST_PLACEHOLDER = "hostnameRequest";
|
||||
private static final String HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER = "domainRequest";
|
||||
private static final String IP_PLACEHOLDER = "ip";
|
||||
private static final String PORT_PLACEHOLDER = "port";
|
||||
private static final String PORT_REQUEST_PLACEHOLDER = "portRequest";
|
||||
private static final String TENANT_PLACEHOLDER = "tenant";
|
||||
private static final String CONTEXT_PATH = "contextPath";
|
||||
private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
|
||||
private static final String SOFTWARE_MODULE_ID_PLACEHOLDER = "softwareModuleId";
|
||||
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
|
||||
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
|
||||
// by default, we download via the controller / DDI API download endpoint
|
||||
static final String DEFAULT_URL_PROTOCOL_REF = "{" + PROTOCOL_REQUEST_PLACEHOLDER + "}://{" + HOSTNAME_REQUEST_PLACEHOLDER + "}:{" + PORT_REQUEST_PLACEHOLDER + "}{" + CONTEXT_PATH + "}/{" + TENANT_PLACEHOLDER + "}/controller/v1/{" + CONTROLLER_ID_PLACEHOLDER + "}/softwaremodules/{" + SOFTWARE_MODULE_ID_PLACEHOLDER + "}/artifacts/{" + ARTIFACT_FILENAME_PLACEHOLDER + "}";
|
||||
|
||||
private final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties;
|
||||
private final String contextPath;
|
||||
|
||||
@SuppressWarnings("java:S3358") // better readable this way
|
||||
public PropertyBasedArtifactUrlResolver(final PropertyBasedArtifactUrlResolverProperties urlHandlerProperties, final String contextPath) {
|
||||
this.urlHandlerProperties = urlHandlerProperties;
|
||||
this.contextPath = ObjectUtils.isEmpty(contextPath) || "/".equals(contextPath)
|
||||
? ""
|
||||
: (contextPath.charAt(0) == '/' ? contextPath : '/' + contextPath); // normalize context path
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final DownloadDescriptor downloadDescriptor, final ApiType api) {
|
||||
return getUrls(downloadDescriptor, api, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final DownloadDescriptor downloadDescriptor, final ApiType api, final URI requestUri) {
|
||||
return urlHandlerProperties.getProtocols().values().stream()
|
||||
.filter(urlProtocol -> urlProtocol.isEnabled() && urlProtocol.getSupports().contains(api))
|
||||
.map(urlProtocol -> new ArtifactUrl(
|
||||
urlProtocol.getProtocol().toUpperCase(), urlProtocol.getRel(),
|
||||
generateUrl(urlProtocol, downloadDescriptor, requestUri)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String generateUrl(final UrlProtocol protocol, final DownloadDescriptor placeholder, final URI requestUri) {
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder, requestUri).entrySet();
|
||||
String urlPattern = protocol.getRef();
|
||||
for (final Entry<String, String> entry : entrySet) {
|
||||
if (List.of(PORT_PLACEHOLDER, PORT_REQUEST_PLACEHOLDER).contains(entry.getKey())) {
|
||||
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
|
||||
ObjectUtils.isEmpty(entry.getValue()) ? "" : (":" + entry.getValue()));
|
||||
} else {
|
||||
if (entry.getValue() != null) {
|
||||
urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return urlPattern;
|
||||
}
|
||||
|
||||
private Map<String, String> getReplaceMap(final UrlProtocol protocol, final DownloadDescriptor placeholder, final URI requestUri) {
|
||||
final Map<String, String> replaceMap = new HashMap<>();
|
||||
|
||||
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
|
||||
replaceMap.put(PROTOCOL_REQUEST_PLACEHOLDER, Optional.ofNullable(requestUri).map(URI::getScheme).orElseGet(protocol::getProtocol));
|
||||
|
||||
replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
|
||||
replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
|
||||
replaceMap.put(HOSTNAME_REQUEST_PLACEHOLDER, Optional.ofNullable(requestUri).map(URI::getHost).orElseGet(protocol::getHostname));
|
||||
replaceMap.put(HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER, computeHostWithRequestDomain(protocol, requestUri));
|
||||
|
||||
replaceMap.put(PORT_PLACEHOLDER, getPort(protocol));
|
||||
replaceMap.put(
|
||||
PORT_REQUEST_PLACEHOLDER,
|
||||
Optional.ofNullable(requestUri)
|
||||
.map(URI::getPort)
|
||||
.map(port -> port > 0 ? String.valueOf(port) : "")
|
||||
.orElseGet(() -> getPort(protocol)));
|
||||
|
||||
replaceMap.put(CONTEXT_PATH, contextPath);
|
||||
|
||||
replaceMap.put(TENANT_PLACEHOLDER, placeholder.tenant());
|
||||
replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.controllerId());
|
||||
replaceMap.put(SOFTWARE_MODULE_ID_PLACEHOLDER, String.valueOf(placeholder.softwareModuleId()));
|
||||
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, URLEncoder.encode(placeholder.filename(), StandardCharsets.UTF_8));
|
||||
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.sha1());
|
||||
return replaceMap;
|
||||
}
|
||||
|
||||
private static String getPort(final UrlProtocol protocol) {
|
||||
return ObjectUtils.isEmpty(protocol.getPort()) ? null : String.valueOf(protocol.getPort());
|
||||
}
|
||||
|
||||
private static String computeHostWithRequestDomain(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
if (!protocol.getHostname().contains(".")) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
final List<String> domainElements = Arrays.asList(StringUtils.delimitedListToStringArray(requestUri.getHost(), "."));
|
||||
if (domainElements.isEmpty()) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
final String domain = StringUtils.collectionToDelimitedString(domainElements.subList(1, domainElements.size()), ".");
|
||||
return StringUtils.delimitedListToStringArray(protocol.getHostname(), ".")[0].trim() + "." + domain;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
package org.eclipse.hawkbit.artifact.urlresolver;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -19,14 +19,13 @@ import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Artifact handler properties class for holding all supported protocols with
|
||||
* host, ip, port and download pattern.
|
||||
* Artifact handler properties class for holding all supported protocols with host, ip, port and download pattern.
|
||||
*
|
||||
* @see PropertyBasedArtifactUrlHandler
|
||||
* @see PropertyBasedArtifactUrlResolver
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("hawkbit.artifact.url")
|
||||
public class ArtifactUrlHandlerProperties {
|
||||
public class PropertyBasedArtifactUrlResolverProperties {
|
||||
|
||||
/**
|
||||
* Rel as key and complete protocol as value.
|
||||
@@ -53,9 +52,9 @@ public class ArtifactUrlHandlerProperties {
|
||||
|
||||
/**
|
||||
* Hypermedia ref pattern for this protocol. Supported placeholders are the properties
|
||||
* supported by {@link PropertyBasedArtifactUrlHandler}.
|
||||
* supported by {@link PropertyBasedArtifactUrlResolver}.
|
||||
*/
|
||||
private String ref = PropertyBasedArtifactUrlHandler.DEFAULT_URL_PROTOCOL_REF;
|
||||
private String ref = PropertyBasedArtifactUrlResolver.DEFAULT_URL_PROTOCOL_REF;
|
||||
|
||||
/**
|
||||
* Protocol name placeholder that can be used in ref pattern.
|
||||
@@ -70,8 +69,6 @@ public class ArtifactUrlHandlerProperties {
|
||||
/**
|
||||
* IP address placeholder that can be used in ref pattern.
|
||||
*/
|
||||
// Exception squid:S1313 - default only, can be configured
|
||||
@SuppressWarnings("squid:S1313")
|
||||
private String ip = "127.0.0.1";
|
||||
|
||||
/**
|
||||
@@ -82,9 +79,9 @@ public class ArtifactUrlHandlerProperties {
|
||||
/**
|
||||
* Support for the following hawkBit API.
|
||||
*/
|
||||
private List<ApiType> supports = Arrays.asList(ApiType.DDI, ApiType.DMF, ApiType.MGMT);
|
||||
private List<ArtifactUrlResolver.ApiType> supports = Arrays.asList(ArtifactUrlResolver.ApiType.DDI, ArtifactUrlResolver.ApiType.DMF, ArtifactUrlResolver.ApiType.MGMT);
|
||||
|
||||
public void setSupports(final List<ApiType> supports) {
|
||||
public void setSupports(final List<ArtifactUrlResolver.ApiType> supports) {
|
||||
this.supports = Collections.unmodifiableList(supports);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.model;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface definition for artifact binary.
|
||||
*/
|
||||
public interface DbArtifact {
|
||||
|
||||
/**
|
||||
* @return ID of the artifact
|
||||
*/
|
||||
String getArtifactId();
|
||||
|
||||
/**
|
||||
* @return hashes of the artifact
|
||||
*/
|
||||
DbArtifactHash getHashes();
|
||||
|
||||
/**
|
||||
* @return size of the artifact in bytes
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
/**
|
||||
* @return content-type if known by the repository or <code>null</code>
|
||||
*/
|
||||
String getContentType();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
InputStream getFileInputStream();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Database representation of artifact hash.
|
||||
*/
|
||||
@Data
|
||||
public class DbArtifactHash {
|
||||
|
||||
private final String sha1;
|
||||
private final String md5;
|
||||
private final String sha256;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param sha1 the sha1 hash
|
||||
* @param md5 the md5 hash
|
||||
* @param sha256 the sha256 hash
|
||||
*/
|
||||
public DbArtifactHash(final String sha1, final String md5, final String sha256) {
|
||||
this.sha1 = sha1;
|
||||
this.md5 = md5;
|
||||
this.sha256 = sha256;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
/**
|
||||
* hawkBit API type.
|
||||
*/
|
||||
public enum ApiType {
|
||||
|
||||
/**
|
||||
* Support for Device Management Federation API.
|
||||
*/
|
||||
DMF,
|
||||
|
||||
/**
|
||||
* Support for Direct Device Integration API.
|
||||
*/
|
||||
DDI,
|
||||
|
||||
/**
|
||||
* Support for Management API.
|
||||
*/
|
||||
MGMT
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Container for a generated Artifact URL.
|
||||
*/
|
||||
@Data
|
||||
public class ArtifactUrl {
|
||||
|
||||
private final String protocol;
|
||||
private final String rel;
|
||||
private final String ref;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param protocol string, e.g. ftp, http, https
|
||||
* @param rel hypermedia value
|
||||
* @param ref hypermedia value
|
||||
*/
|
||||
public ArtifactUrl(final String protocol, final String rel, final String ref) {
|
||||
this.protocol = protocol;
|
||||
this.rel = rel;
|
||||
this.ref = ref;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface declaration of the {@link ArtifactUrlHandler} which generates the URLs to specific artifacts.
|
||||
*/
|
||||
public interface ArtifactUrlHandler {
|
||||
|
||||
/**
|
||||
* Returns a generated download URL for a given artifact parameters for a specific protocol.
|
||||
*
|
||||
* @param placeholder data for URL generation
|
||||
* @param api given protocol that URL needs to support
|
||||
* @return a URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api);
|
||||
|
||||
/**
|
||||
* Returns a generated download URL for a given artifact parameters for a
|
||||
* specific protocol.
|
||||
*
|
||||
* @param placeholder data for URL generation
|
||||
* @param api given protocol that URL needs to support
|
||||
* @param requestUri of the request that allows the handler to align the generated URL to the original request.
|
||||
* @return a URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api, URI requestUri);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Utility class for Base10 to Base62 conversion and vice versa. Base62 has the benefit of being shorter in ASCII representation than Base10.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
final class Base62Util {
|
||||
|
||||
private static final String BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
private static final int BASE62_BASE = BASE62_ALPHABET.length();
|
||||
|
||||
/**
|
||||
* @param l number
|
||||
* @return converted number into Base62 ASCII string
|
||||
*/
|
||||
static String fromBase10(final long l) {
|
||||
if (l == 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
long temp = l;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (temp > 0) {
|
||||
temp = fromBase10(temp, sb);
|
||||
}
|
||||
return sb.reverse().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param base62 number
|
||||
* @return converted number into Base10
|
||||
*/
|
||||
static Long toBase10(final String base62) {
|
||||
return toBase10(new StringBuilder(base62).reverse().toString().toCharArray());
|
||||
}
|
||||
|
||||
static Long fromBase10(final long l, final StringBuilder sb) {
|
||||
final int rem = (int) (l % BASE62_BASE);
|
||||
sb.append(BASE62_ALPHABET.charAt(rem));
|
||||
return l / BASE62_BASE;
|
||||
}
|
||||
|
||||
private static Long toBase10(final char[] chars) {
|
||||
long base10 = 0L;
|
||||
for (int i = chars.length - 1; i >= 0; i--) {
|
||||
base10 += toBase10(BASE62_ALPHABET.indexOf(chars[i]), i);
|
||||
}
|
||||
return base10;
|
||||
}
|
||||
|
||||
private static int toBase10(final int n, final int pow) {
|
||||
return n * (int) Math.pow(BASE62_BASE, pow);
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties.UrlProtocol;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation for ArtifactUrlHandler for creating urls to download resource based on patterns configured by
|
||||
* {@link ArtifactUrlHandlerProperties}.
|
||||
*
|
||||
* This mechanism can be used to generate links to arbitrary file hosting infrastructure. However, the hawkBit update server
|
||||
* supports hosting files as well in the following {@link UrlProtocol#getRef()} patterns:
|
||||
*
|
||||
* Default:
|
||||
* {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}
|
||||
* /softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
|
||||
*
|
||||
* Default (MD5SUM files):
|
||||
* {protocol}://{hostname}:{port}{contextPath}/{tenant}/controller/v1/{controllerId}/
|
||||
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
|
||||
*/
|
||||
public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
|
||||
|
||||
private static final String PROTOCOL_PLACEHOLDER = "protocol";
|
||||
private static final String HOSTNAME_PLACEHOLDER = "hostname";
|
||||
private static final String IP_PLACEHOLDER = "ip";
|
||||
private static final String PORT_PLACEHOLDER = "port";
|
||||
private static final String CONTEXT_PATH = "contextPath";
|
||||
private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
|
||||
private static final String TARGET_ID_BASE10_PLACEHOLDER = "targetId";
|
||||
private static final String TARGET_ID_BASE62_PLACEHOLDER = "targetIdBase62";
|
||||
private static final String HOSTNAME_REQUEST_PLACEHOLDER = "hostnameRequest";
|
||||
private static final String PORT_REQUEST_PLACEHOLDER = "portRequest";
|
||||
private static final String PROTOCOL_REQUEST_PLACEHOLDER = "protocolRequest";
|
||||
private static final String HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER = "domainRequest";
|
||||
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
|
||||
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
|
||||
private static final String ARTIFACT_ID_BASE10_PLACEHOLDER = "artifactId";
|
||||
private static final String ARTIFACT_ID_BASE62_PLACEHOLDER = "artifactIdBase62";
|
||||
private static final String TENANT_PLACEHOLDER = "tenant";
|
||||
private static final String TENANT_ID_BASE10_PLACEHOLDER = "tenantId";
|
||||
private static final String TENANT_ID_BASE62_PLACEHOLDER = "tenantIdBase62";
|
||||
private static final String SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER = "softwareModuleId";
|
||||
private static final String SOFTWARE_MODULE_ID_BASE62_PLACEHOLDER = "softwareModuleIdBase62";
|
||||
static final String DEFAULT_URL_PROTOCOL_REF = "{" + PROTOCOL_PLACEHOLDER + "}://{" + HOSTNAME_PLACEHOLDER + "}:{" + PORT_PLACEHOLDER + "}{" + CONTEXT_PATH + "}/{" + TENANT_PLACEHOLDER + "}/controller/v1/{" + CONTROLLER_ID_PLACEHOLDER + "}/softwaremodules/{" + SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER + "}/artifacts/{" + ARTIFACT_FILENAME_PLACEHOLDER + "}";
|
||||
|
||||
private final ArtifactUrlHandlerProperties urlHandlerProperties;
|
||||
private final String contextPath;
|
||||
|
||||
/**
|
||||
* @param urlHandlerProperties for URL generation configuration
|
||||
*/
|
||||
public PropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties, final String contextPath) {
|
||||
this.urlHandlerProperties = urlHandlerProperties;
|
||||
this.contextPath = contextPath == null || "/".equals(contextPath) ? "" : contextPath; // normalize
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api) {
|
||||
return getUrls(placeholder, api, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api, final URI requestUri) {
|
||||
return urlHandlerProperties.getProtocols().values().stream()
|
||||
.filter(urlProtocol -> urlProtocol.getSupports().contains(api) && urlProtocol.isEnabled())
|
||||
.map(urlProtocol -> new ArtifactUrl(urlProtocol.getProtocol().toUpperCase(), urlProtocol.getRel(),
|
||||
generateUrl(urlProtocol, placeholder, requestUri)))
|
||||
.toList();
|
||||
|
||||
}
|
||||
|
||||
private static String getRequestPort(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return getPort(protocol);
|
||||
}
|
||||
// if port undefined then default protocol port is used
|
||||
return requestUri.getPort() > 0 ? String.valueOf(requestUri.getPort()) : "";
|
||||
}
|
||||
|
||||
private static String getRequestHost(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(requestUri.getHost()).orElse(protocol.getHostname());
|
||||
}
|
||||
|
||||
private static String getRequestProtocol(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return protocol.getProtocol();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(requestUri.getScheme()).orElse(protocol.getProtocol());
|
||||
}
|
||||
|
||||
private static String getPort(final UrlProtocol protocol) {
|
||||
return ObjectUtils.isEmpty(protocol.getPort()) ? null : String.valueOf(protocol.getPort());
|
||||
}
|
||||
|
||||
private static String computeHostWithRequestDomain(final UrlProtocol protocol, final URI requestUri) {
|
||||
|
||||
if (requestUri == null) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
if (!protocol.getHostname().contains(".")) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
final String host = StringUtils.delimitedListToStringArray(protocol.getHostname(), ".")[0].trim();
|
||||
|
||||
final List<String> domainElements = Arrays
|
||||
.asList(StringUtils.delimitedListToStringArray(requestUri.getHost(), "."));
|
||||
final String domain = StringUtils.collectionToDelimitedString(domainElements.subList(1, domainElements.size()),
|
||||
".");
|
||||
|
||||
if (ObjectUtils.isEmpty(domain)) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
return host + "." + domain;
|
||||
}
|
||||
|
||||
private String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder,
|
||||
final URI requestUri) {
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder, requestUri).entrySet();
|
||||
|
||||
String urlPattern = protocol.getRef();
|
||||
|
||||
for (final Entry<String, String> entry : entrySet) {
|
||||
if (List.of(PORT_PLACEHOLDER, PORT_REQUEST_PLACEHOLDER).contains(entry.getKey())) {
|
||||
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
|
||||
ObjectUtils.isEmpty(entry.getValue()) ? "" : (":" + entry.getValue()));
|
||||
} else {
|
||||
if (entry.getValue() != null) {
|
||||
urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlPattern;
|
||||
}
|
||||
|
||||
private Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder,
|
||||
final URI requestUri) {
|
||||
final Map<String, String> replaceMap = new HashMap<>();
|
||||
replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
|
||||
|
||||
replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
|
||||
|
||||
replaceMap.put(HOSTNAME_REQUEST_PLACEHOLDER, getRequestHost(protocol, requestUri));
|
||||
replaceMap.put(PORT_REQUEST_PLACEHOLDER, getRequestPort(protocol, requestUri));
|
||||
replaceMap.put(HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER, computeHostWithRequestDomain(protocol, requestUri));
|
||||
replaceMap.put(PROTOCOL_REQUEST_PLACEHOLDER, getRequestProtocol(protocol, requestUri));
|
||||
|
||||
replaceMap.put(CONTEXT_PATH, contextPath);
|
||||
|
||||
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
|
||||
URLEncoder.encode(placeholder.getSoftwareData().getFilename(), StandardCharsets.UTF_8));
|
||||
|
||||
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
|
||||
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
|
||||
replaceMap.put(PORT_PLACEHOLDER, getPort(protocol));
|
||||
replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
|
||||
replaceMap.put(TENANT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTenantId()));
|
||||
replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
|
||||
replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.getControllerId());
|
||||
replaceMap.put(TARGET_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTargetId()));
|
||||
if (placeholder.getTargetId() != null) {
|
||||
replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
|
||||
}
|
||||
replaceMap.put(ARTIFACT_ID_BASE62_PLACEHOLDER,
|
||||
Base62Util.fromBase10(placeholder.getSoftwareData().getArtifactId()));
|
||||
replaceMap.put(ARTIFACT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getSoftwareData().getArtifactId()));
|
||||
replaceMap.put(SOFTWARE_MODULE_ID_BASE10_PLACEHOLDER,
|
||||
String.valueOf(placeholder.getSoftwareData().getSoftwareModuleId()));
|
||||
replaceMap.put(SOFTWARE_MODULE_ID_BASE62_PLACEHOLDER,
|
||||
Base62Util.fromBase10(placeholder.getSoftwareData().getSoftwareModuleId()));
|
||||
return replaceMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Container for variables available to the {@link ArtifactUrlHandler}.
|
||||
*/
|
||||
@Data
|
||||
public class URLPlaceholder {
|
||||
|
||||
private final String tenant;
|
||||
private final Long tenantId;
|
||||
private final String controllerId;
|
||||
private final Long targetId;
|
||||
private final SoftwareData softwareData;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param tenant of the client
|
||||
* @param tenantId of teh tenant
|
||||
* @param controllerId of the target
|
||||
* @param targetId of the target
|
||||
* @param softwareData information about the artifact and software module that can be accessed by the URL.
|
||||
*/
|
||||
public URLPlaceholder(final String tenant, final Long tenantId, final String controllerId, final Long targetId,
|
||||
final SoftwareData softwareData) {
|
||||
this.tenant = tenant;
|
||||
this.tenantId = tenantId;
|
||||
this.controllerId = controllerId;
|
||||
this.targetId = targetId;
|
||||
this.softwareData = softwareData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the artifact and software module that can be accessed
|
||||
* by the URL.
|
||||
*/
|
||||
@Data
|
||||
public static class SoftwareData {
|
||||
|
||||
private Long softwareModuleId;
|
||||
private String filename;
|
||||
private Long artifactId;
|
||||
private String sha1Hash;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param softwareModuleId of the module the artifact belongs to
|
||||
* @param filename of the artifact
|
||||
* @param artifactId of the artifact
|
||||
* @param sha1Hash of the artifact
|
||||
*/
|
||||
public SoftwareData(final Long softwareModuleId, final String filename, final Long artifactId, final String sha1Hash) {
|
||||
this.softwareModuleId = softwareModuleId;
|
||||
this.filename = filename;
|
||||
this.artifactId = artifactId;
|
||||
this.sha1Hash = sha1Hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.encryption;
|
||||
package org.eclipse.hawkbit.artifact.encryption;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.eclipse.hawkbit.artifact.exception.ArtifactEncryptionUnsupportedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
package org.eclipse.hawkbit.artifact.urlresolver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -15,8 +15,8 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties.UrlProtocol;
|
||||
import org.eclipse.hawkbit.repository.artifact.urlhandler.URLPlaceholder.SoftwareData;
|
||||
import org.eclipse.hawkbit.artifact.urlresolver.ArtifactUrlResolver.DownloadDescriptor;
|
||||
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties.UrlProtocol;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -29,55 +29,44 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
* Story: Test to generate the artifact download URL
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PropertyBasedArtifactUrlHandlerTest {
|
||||
class PropertyBasedArtifactUrlResolverTest {
|
||||
|
||||
private static final String TEST_PROTO = "coap";
|
||||
private static final String TEST_PROTO = "https";
|
||||
private static final String TEST_REL = "download-udp";
|
||||
|
||||
private static final long TENANT_ID = 456789L;
|
||||
private static final String CONTROLLER_ID = "Test";
|
||||
private static final String FILENAME_DECODE = "test123!§$%&";
|
||||
private static final String FILENAME_ENCODE = "test123%21%C2%A7%24%25%26";
|
||||
private static final long SOFTWARE_MODULE_ID = 87654L;
|
||||
private static final long TARGET_ID = 3474366L;
|
||||
private static final String TARGET_ID_BASE62 = "EZqA";
|
||||
private static final String SHA1HASH = "test12345";
|
||||
private static final long ARTIFACT_ID = 1345678L;
|
||||
private static final String ARTIFACT_ID_BASE62 = "5e4U";
|
||||
private static final String TENANT = "TEST_TENANT";
|
||||
private static final String CONTROLLER_ID = "Test";
|
||||
private static final long SOFTWARE_MODULE_ID = 87654L;
|
||||
private static final String FILENAME = "test123!§$%&";
|
||||
private static final String FILENAME_ENCODED = "test123%21%C2%A7%24%25%26";
|
||||
private static final String SHA1 = "0123456789012345678901234567890123456789";
|
||||
|
||||
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
|
||||
private static final URLPlaceholder placeHolder = new URLPlaceholder(
|
||||
TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
|
||||
new SoftwareData(SOFTWARE_MODULE_ID, FILENAME_DECODE, ARTIFACT_ID, SHA1HASH));
|
||||
private ArtifactUrlHandler urlHandlerUnderTest;
|
||||
private ArtifactUrlHandlerProperties properties;
|
||||
private static final DownloadDescriptor DOWNLOAD_DESCRIPTOR = new DownloadDescriptor(TENANT, CONTROLLER_ID, SOFTWARE_MODULE_ID, FILENAME,
|
||||
SHA1);
|
||||
|
||||
private ArtifactUrlResolver urlHandlerUnderTest;
|
||||
private PropertyBasedArtifactUrlResolverProperties properties;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
properties = new ArtifactUrlHandlerProperties();
|
||||
urlHandlerUnderTest = new PropertyBasedArtifactUrlHandler(properties, "");
|
||||
properties = new PropertyBasedArtifactUrlResolverProperties();
|
||||
urlHandlerUnderTest = new PropertyBasedArtifactUrlResolver(properties, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the generation of http download url.
|
||||
*/
|
||||
@Test
|
||||
void urlGenerationWithDefaultConfiguration() {
|
||||
properties.getProtocols().put("download-http", new UrlProtocol());
|
||||
|
||||
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI);
|
||||
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI);
|
||||
assertThat(ddiUrls).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
HTTP_LOCALHOST + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE))
|
||||
.isEqualTo(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF));
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
HTTP_LOCALHOST + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED))
|
||||
.isEqualTo(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the generation of custom download url with a CoAP example that supports DMF only.
|
||||
*/
|
||||
@Test
|
||||
void urlGenerationWithCustomConfiguration() {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
@@ -85,41 +74,17 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(List.of(ApiType.DMF));
|
||||
proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactSHA1}");
|
||||
proto.setSupports(List.of(ArtifactUrlResolver.ApiType.DMF));
|
||||
proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactFileName}");
|
||||
properties.getProtocols().put(TEST_PROTO, proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI)).isEmpty();
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI)).isEmpty();
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
|
||||
new ArtifactUrl(
|
||||
TEST_PROTO.toUpperCase(), TEST_REL,
|
||||
"coap://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + SHA1HASH));
|
||||
TEST_PROTO + "://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + FILENAME_ENCODED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the generation of custom download url using Base62 references with a CoAP example that supports DMF only.
|
||||
*/
|
||||
@Test
|
||||
void urlGenerationWithCustomShortConfiguration() {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setIp("127.0.0.1");
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(List.of(ApiType.DMF));
|
||||
proto.setRef("{protocol}://{ip}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
|
||||
properties.getProtocols().put("ftp", proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI)).isEmpty();
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
|
||||
new ArtifactUrl(
|
||||
TEST_PROTO.toUpperCase(), TEST_REL,
|
||||
TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the full qualified host of the statically defined hostname is replaced with the host of the request.
|
||||
*/
|
||||
@Test
|
||||
void urlGenerationWithHostFromRequest() throws URISyntaxException {
|
||||
final String testHost = "ddi.host.com";
|
||||
@@ -129,14 +94,15 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(List.of(ApiType.DDI));
|
||||
proto.setRef("{protocol}://{hostnameRequest}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
|
||||
proto.setSupports(List.of(ArtifactUrlResolver.ApiType.DDI));
|
||||
proto.setRef("{protocol}://{hostnameRequest}:{port}/fws/{tenant}/{controllerId}/{artifactFileName}");
|
||||
properties.getProtocols().put("ftp", proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("https://" + testHost))).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
|
||||
new URI("https://" + testHost))).containsExactly(
|
||||
new ArtifactUrl(
|
||||
TEST_PROTO.toUpperCase(), TEST_REL,
|
||||
TEST_PROTO + "://" + testHost + ":5683/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
|
||||
TEST_PROTO + "://" + testHost + ":5683/fws/" + TENANT + "/" + CONTROLLER_ID + "/" + FILENAME_ENCODED));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,13 +113,14 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
final String testHost = "ddi.host.com";
|
||||
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setRef("{protocolRequest}://{hostname}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
|
||||
proto.setRef("{protocolRequest}://{hostname}:{port}/fws/{tenant}/{controllerId}/{artifactFileName}");
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("https://" + testHost))).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
|
||||
new URI("https://" + testHost))).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
"https://localhost:8080/fws/" + TENANT + "/" + TARGET_ID_BASE62 + "/" + ARTIFACT_ID_BASE62));
|
||||
"https://localhost:8080/fws/" + TENANT + "/" + CONTROLLER_ID + "/" + FILENAME_ENCODED));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,21 +129,23 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
@Test
|
||||
void urlGenerationWithPortFromRequest() throws URISyntaxException {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setRef("{protocol}://{hostname}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
proto.setRef(
|
||||
"{protocol}://{hostname}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("http://anotherHost.com:8083"))).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
|
||||
new URI("http://anotherHost.com:8083"))).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
"http://localhost:8083/" + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
"http://localhost:8080/" + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,17 +155,16 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
void urlGenerationWithPortFromRequestForHttps() throws URISyntaxException {
|
||||
final String protocol = "https";
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setRef("{protocolRequest}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
proto.setRef(
|
||||
"{protocolRequest}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
proto.setProtocol(protocol);
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
final URI uri = new URI(protocol + "://anotherHost.com");
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, uri)).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI, uri)).containsExactly(
|
||||
new ArtifactUrl(
|
||||
protocol.toUpperCase(), "download-http",
|
||||
uri + "/" + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
|
||||
|
||||
uri + "/" + TENANT + "/controller/v1/" + CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,20 +174,22 @@ class PropertyBasedArtifactUrlHandlerTest {
|
||||
void urlGenerationWithDomainFromRequest() throws URISyntaxException {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setHostname("host.bumlux.net");
|
||||
proto.setRef("{protocol}://{domainRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
proto.setRef(
|
||||
"{protocol}://{domainRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DDI, new URI("http://anotherHost.com:8083"))).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DDI,
|
||||
new URI("http://anotherHost.com:8083"))).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
"http://host.com/" + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
|
||||
|
||||
assertThat(urlHandlerUnderTest.getUrls(placeHolder, ApiType.DMF)).containsExactly(
|
||||
assertThat(urlHandlerUnderTest.getUrls(DOWNLOAD_DESCRIPTOR, ArtifactUrlResolver.ApiType.DMF)).containsExactly(
|
||||
new ArtifactUrl(
|
||||
"http".toUpperCase(), "download-http",
|
||||
"http://host.bumlux.net/" + TENANT + "/controller/v1/" +
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODE));
|
||||
CONTROLLER_ID + "/softwaremodules/" + SOFTWARE_MODULE_ID + "/artifacts/" + FILENAME_ENCODED));
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Feature: Unit Tests - Artifact URL Handler<br/>
|
||||
* Story: Base62 Utility tests
|
||||
*/
|
||||
class Base62UtilTest {
|
||||
|
||||
/**
|
||||
* Convert Base10 numbers to Base62 ASCII strings.
|
||||
*/
|
||||
@Test
|
||||
void fromBase10() {
|
||||
assertThat(Base62Util.fromBase10(0L)).isEqualTo("0");
|
||||
assertThat(Base62Util.fromBase10(11L)).isEqualTo("B");
|
||||
assertThat(Base62Util.fromBase10(36L)).isEqualTo("a");
|
||||
assertThat(Base62Util.fromBase10(999L)).isEqualTo("G7");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Base62 ASCII strings to Base10 numbers.
|
||||
*/
|
||||
@Test
|
||||
void toBase10() {
|
||||
assertThat(Base62Util.toBase10("0")).isZero();
|
||||
assertThat(Base62Util.toBase10("B")).isEqualTo(11);
|
||||
assertThat(Base62Util.toBase10("a")).isEqualTo(36L);
|
||||
assertThat(Base62Util.toBase10("G7")).isEqualTo(999L);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.artifact.urlhandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Feature: Unit Tests - Artifact URL Handler<br/>
|
||||
* Story: URL placeholder tests
|
||||
*/
|
||||
class URLPlaceholderTest {
|
||||
|
||||
private final URLPlaceholder.SoftwareData softwareData;
|
||||
private final URLPlaceholder placeholder;
|
||||
|
||||
public URLPlaceholderTest() {
|
||||
this.softwareData = new URLPlaceholder.SoftwareData(1L, "file.txt", 123L, "someHash123");
|
||||
this.placeholder = new URLPlaceholder("SuperCorp", 123L, "Super-1", 1L, softwareData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same object should be equal
|
||||
*/
|
||||
@Test
|
||||
// Exception squid:S5785 - JUnit assertTrue/assertFalse should be simplified to the corresponding dedicated assertion
|
||||
// Need to test the equals method and need to bypass magic logic in utility classes
|
||||
@SuppressWarnings({ "squid:S5838" })
|
||||
void sameObjectShouldBeEqual() {
|
||||
assertThat(softwareData.equals(softwareData)).isTrue();
|
||||
assertThat(placeholder.equals(placeholder)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Different object should not be equal
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings({ "squid:S5838" })
|
||||
void differentObjectShouldNotBeEqual() {
|
||||
final URLPlaceholder.SoftwareData softwareData2 = new URLPlaceholder.SoftwareData(2L, "file.txt", 123L, "someHash123");
|
||||
final URLPlaceholder placeholder2 = new URLPlaceholder("SuperCorp", 123L, "Super-2", 2L, softwareData2);
|
||||
final URLPlaceholder placeholderWithOtherSoftwareData = new URLPlaceholder(placeholder.getTenant(),
|
||||
placeholder.getTenantId(), placeholder.getControllerId(), placeholder.getTargetId(), softwareData2);
|
||||
assertThat(placeholder.equals(placeholder2)).isFalse();
|
||||
assertThat(placeholder2.equals(placeholder)).isFalse();
|
||||
assertThat(softwareData.equals(softwareData2)).isFalse();
|
||||
assertThat(softwareData2.equals(softwareData)).isFalse();
|
||||
assertThat(placeholder.equals(placeholderWithOtherSoftwareData)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Different objects with same properties should be equal
|
||||
*/
|
||||
@Test
|
||||
void differentObjectsWithSamePropertiesShouldBeEqual() {
|
||||
final URLPlaceholder placeholderWithSameProperties = new URLPlaceholder(placeholder.getTenant(), placeholder.getTenantId(),
|
||||
placeholder.getControllerId(), placeholder.getTargetId(), softwareData);
|
||||
assertThat(placeholder).isEqualTo(placeholderWithSameProperties);
|
||||
assertThat(placeholderWithSameProperties).isEqualTo(placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not equal null
|
||||
*/
|
||||
@Test
|
||||
// Exception squid:S5785 - JUnit assertTrue/assertFalse should be simplified to
|
||||
// the corresponding dedicated assertion
|
||||
// Need to test the equals method and need to bypass magic logic in utility
|
||||
// classes
|
||||
@SuppressWarnings({ "squid:S5838" })
|
||||
void shouldNotEqualNull() {
|
||||
assertThat(placeholder.equals(null)).isFalse();
|
||||
assertThat(softwareData.equals(null)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* HashCode should not change
|
||||
*/
|
||||
@Test
|
||||
void hashCodeShouldNotChange() {
|
||||
final URLPlaceholder placeholderWithSameProperties = new URLPlaceholder(placeholder.getTenant(), placeholder.getTenantId(),
|
||||
placeholder.getControllerId(), placeholder.getTargetId(), softwareData);
|
||||
assertThat(placeholder).hasSameHashCodeAs(placeholder).hasSameHashCodeAs(placeholderWithSameProperties);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user