diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java b/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java new file mode 100644 index 0000000..603c8df --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java @@ -0,0 +1,23 @@ +package be.seeseepuff.pcinv.controllers; + +import be.seeseepuff.pcinv.services.AssetService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class StartupController { + private final AssetService assetService; + + @PostConstruct + public void init() { + var descriptors = assetService.getAssetDescriptors(); + log.info("Asset descriptors loaded:\n{}", descriptors); + + var assets = assetService.countAssets(); + log.info("The repository contains {} assets.", assets); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java new file mode 100644 index 0000000..029e30e --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java @@ -0,0 +1,34 @@ +package be.seeseepuff.pcinv.meta; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Holds the descriptors for all possible asset types. + */ +@Getter +public class AssetDescriptors { + /// A collection of all types of assets. + private final Collection assets = new ArrayList<>(); + + /** + * Loads the asset properties from a given asset type. + * + * @param assetType The type of the asset to load properties for. + */ + public void loadFrom(Class assetType) { + var property = AssetProperties.loadFrom(assetType); + assets.add(property); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append("AssetDescriptors [\n"); + assets.forEach(assetProperties -> builder.append(assetProperties.toString(" ")).append('\n')); + builder.append("]"); + return builder.toString(); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java new file mode 100644 index 0000000..40dcb49 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java @@ -0,0 +1,34 @@ +package be.seeseepuff.pcinv.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Provides metadata about an asset type itself. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AssetInfo { + /** + * The displayable name of the asset type. + * + * @return the display name of the asset type + */ + String displayName() default ""; + + /** + * The type of the asset, which can be a string or an integer. + * + * @return the type of the asset + */ + String type() default ""; + + /** + * Indicates whether the asset type should be visible in the UI. + * + * @return true if the asset type is visible, false otherwise + */ + boolean isVisible() default true; +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java new file mode 100644 index 0000000..3efed5b --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java @@ -0,0 +1,69 @@ +package be.seeseepuff.pcinv.meta; + +import lombok.Builder; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; + +/** + * Describes the properties of an asset + */ +@Builder +public class AssetProperties { + /// The type of property, e.g.: ram, asset, etc... + private final String type; + + /// The displayable name of the property, e.g.: "Random Access memory" + private final String displayName; + + /// Whether the asset is visible in the user interface. + private final boolean visible; + + /// The properties of the asset, such as "brand", "model", etc. + @lombok.Singular + private Collection properties; + + /** + * Loads the asset properties from a given asset class. + * + * @param assetType The class of the asset to load properties for. + * @return An AssetProperties instance containing the loaded properties. + */ + public static AssetProperties loadFrom(Class assetType) { + var assetInfo = assetType.getAnnotation(AssetInfo.class); + Objects.requireNonNull(assetInfo, "Asset class must be annotated with @AssetInfo"); + return AssetProperties.builder() + .type(assetInfo.type()) + .displayName(assetInfo.displayName()) + .visible(assetInfo.isVisible()) + .properties(Arrays.stream(assetType.getDeclaredFields()) + .map(AssetProperty::loadFrom) + .filter(Objects::nonNull) + .toList()) + .build(); + } + + /** + * Returns a string representation of the asset properties with the specified indentation. + * + * @param indent The indentation to use for formatting. + * @return A formatted string representation of the asset properties. + */ + public String toString(String indent) { + var builder = new StringBuilder(); + builder.append(indent).append("AssetProperties {\n"); + builder.append(indent).append(" type='").append(type).append("',\n"); + builder.append(indent).append(" displayName='").append(displayName).append("',\n"); + if (!visible) { + builder.append(indent).append(" hidden").append(",\n"); + } + builder.append(indent).append(" properties=[\n"); + for (var property : properties) { + builder.append(indent).append(" ").append(property.toString()).append("\n"); + } + builder.append(indent).append(" ]\n"); + builder.append(indent).append('}'); + return builder.toString(); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java new file mode 100644 index 0000000..7ff640b --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -0,0 +1,71 @@ +package be.seeseepuff.pcinv.meta; + +import be.seeseepuff.pcinv.models.AssetCondition; +import jakarta.annotation.Nullable; +import lombok.Builder; + +import java.lang.reflect.Field; + +/** + * Represents a property of an asset, such as its name or type. + * This class is used to define the metadata for assets in the system. + */ +@Builder +public class AssetProperty { + /// The name of the property, e.g., "brand", "model", etc. + private final String name; + /// The name of the property as it should be displayed, e.g., "Brand", "Model", etc. + private final String displayName; + /// The type of the property, which can be a string or an integer. + private final Type type; + + /** + * Enum representing the possible types of asset properties. + */ + public enum Type { + STRING, INTEGER, CONDITION + } + + /** + * Loads an AssetProperty from a given field. + * + * @param property The field representing the property. + * @return An AssetProperty instance with the name, display name, and type determined from the field. + */ + @Nullable + public static AssetProperty loadFrom(Field property) { + var annotation = property.getAnnotation(Property.class); + if (annotation == null) { + return null; + } + return AssetProperty.builder() + .name(property.getName()) + .displayName(annotation.value()) + .type(determineType(property)) + .build(); + } + + /** + * Determines the type of the property based on its field type. + * + * @param property The field representing the property. + * @return The type of the property. + * @throws IllegalArgumentException if the property type is unsupported. + */ + private static Type determineType(Field property) { + if (property.getType() == String.class) { + return Type.STRING; + } else if (property.getType() == Integer.class || property.getType() == int.class || property.getType() == Long.class || property.getType() == long.class) { + return Type.INTEGER; + } else if (property.getType() == AssetCondition.class) { + return Type.CONDITION; + } else { + throw new IllegalArgumentException("Unsupported property type: " + property.getType()); + } + } + + @Override + public String toString() { + return String.format("%s:%s (%s)", name, type.name(), displayName); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/Property.java b/src/main/java/be/seeseepuff/pcinv/meta/Property.java new file mode 100644 index 0000000..eb71c13 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/Property.java @@ -0,0 +1,20 @@ +package be.seeseepuff.pcinv.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to mark a property descriptor of an asset. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Property { + /** + * The displayable name of the property. + * + * @return the display name of the property + */ + String value(); +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/Asset.java b/src/main/java/be/seeseepuff/pcinv/models/Asset.java index a3c6b23..7e295d6 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/Asset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/Asset.java @@ -1,37 +1,7 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.Setter; +public interface Asset { + long getId(); -/** - * Represents a generic asset in the inventory system. - */ -@Getter -@Setter -@Entity -public class Asset -{ - @Id @GeneratedValue - private Long id; - - /// The QR code attached to the asset, used for identification. - private Long qr; - - /// The brand of the asset. - private String brand; - - /// The model of the asset - private String model; - - /// The asset's serial number. - private String serialNumber; - - /// A description of the asset, providing additional details. - private String description; - - /// The state of the asset, indicating its condition. - private AssetCondition condition; + GenericAsset getAsset(); } diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java new file mode 100644 index 0000000..08def8c --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -0,0 +1,52 @@ +package be.seeseepuff.pcinv.models; + +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a generic asset in the inventory system. + */ +@Getter +@Setter +@Entity +@AssetInfo( + displayName = "Asset", + type = "asset", + isVisible = false +) +@Table(name = "assets") +public class GenericAsset +{ + @Id @GeneratedValue + private long id; + + /// The QR code attached to the asset, used for identification. + @Property("QR") + private long qr; + + /// The brand of the asset. + @Property("Brand") + private String brand; + + /// The model of the asset + @Property("Model") + private String model; + + /// The asset's serial number. + @Property("Serial Number") + private String serialNumber; + + /// A description of the asset, providing additional details. + @Property("Description") + private String description; + + /// The state of the asset, indicating its condition. + @Property("Condition") + private AssetCondition condition; +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index 06fc866..ae2cb61 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -1,8 +1,8 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -12,21 +12,30 @@ import lombok.Setter; @Getter @Setter @Entity -public class HddAsset +@AssetInfo( + displayName = "Hard Drive", + type = "HDD" +) +@Table(name = "hdd_assets") +public class HddAsset implements Asset { @Id @GeneratedValue - private Long id; + private long id; - /// The ID of the associated asset, linking it to the generic Asset model. - private Long assetId; + /// The generic asset associated with this HDD. + @OneToOne(orphanRemoval = true) + private GenericAsset asset; /// The capacity of the drive in bytes. - private Long capacity; + @Property("Capacity") + private long capacity; /// The drive's interface type, such as SATA, IDE, ISA-16, ... + @Property("Interface Type") private String interfaceType; /// The drive's form factor, such as 2.5", 3.5", etc. + @Property("Form Factor") private String formFactor; } diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java index 9340450..407a613 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -1,8 +1,8 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -12,18 +12,26 @@ import lombok.Setter; @Getter @Setter @Entity -public class RamAsset +@AssetInfo( + displayName = "Random Access Memory", + type = "RAM" +) +@Table(name = "ram_assets") +public class RamAsset implements Asset { @Id @GeneratedValue - private Long id; + private long id; - /// The ID of the associated asset, linking it to the generic Asset model. - private Long assetId; + /// The generic asset associated with this RAM. + @OneToOne(orphanRemoval = true) + private GenericAsset asset; /// The capacity of the RAM in bytes. - private Long capacity; + @Property("Capacity") + private long capacity; /// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc... + @Property("Type") private String type; } diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index 6f4c96d..09aa689 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -1,8 +1,11 @@ package be.seeseepuff.pcinv.repositories; import be.seeseepuff.pcinv.models.Asset; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository -public interface AssetRepository extends CrudRepository {} +public interface AssetRepository { + Class getAssetType(); + + long count(); +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java new file mode 100644 index 0000000..a989809 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java @@ -0,0 +1,8 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.GenericAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface GenericAssetRepository extends JpaRepository { +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java new file mode 100644 index 0000000..625de83 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java @@ -0,0 +1,12 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.HddAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface HddAssetRepository extends JpaRepository, AssetRepository { + @Override + default Class getAssetType() { + return HddAsset.class; + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java new file mode 100644 index 0000000..2b8bf98 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java @@ -0,0 +1,12 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.RamAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface RamAssetRepository extends JpaRepository, AssetRepository { + @Override + default Class getAssetType() { + return RamAsset.class; + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java new file mode 100644 index 0000000..c56fd8a --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -0,0 +1,39 @@ +package be.seeseepuff.pcinv.services; + +import be.seeseepuff.pcinv.meta.AssetDescriptors; +import be.seeseepuff.pcinv.models.GenericAsset; +import be.seeseepuff.pcinv.repositories.AssetRepository; +import be.seeseepuff.pcinv.repositories.GenericAssetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; + +/** + * Service for managing assets in the repository. + * Provides methods to interact with the asset repositories. + */ +@Service +@RequiredArgsConstructor +public class AssetService { + private final GenericAssetRepository assetRepository; + private final Collection> repositories; + + /** + * Returns the count of all assets in the repository. + * + * @return the total number of assets + */ + public long countAssets() { + return assetRepository.count(); + } + + public AssetDescriptors getAssetDescriptors() { + var descriptors = new AssetDescriptors(); + descriptors.loadFrom(GenericAsset.class); + for (var repository : repositories) { + descriptors.loadFrom(repository.getAssetType()); + } + return descriptors; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 91c83da..1b82a3d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,4 +3,4 @@ server.port=8088 spring.datasource.url=jdbc:postgresql://localhost:5432/pcinv spring.datasource.username=pcinv spring.datasource.password=pcinv -a +spring.jpa.hibernate.ddl-auto=update