Rewrite in Java #1

Merged
seeseemelk merged 22 commits from java into master 2025-06-08 16:10:22 +02:00
10 changed files with 119 additions and 8 deletions
Showing only changes of commit d4718d15c3 - Show all commits

View File

@@ -24,6 +24,7 @@ public class WebController {
private static final String DESCRIPTORS = "descriptors"; private static final String DESCRIPTORS = "descriptors";
/// The name of the model attribute that holds the asset descriptor for the current view. /// The name of the model attribute that holds the asset descriptor for the current view.
private static final String DESCRIPTOR = "descriptor"; private static final String DESCRIPTOR = "descriptor";
private static final String GENERIC_DESCRIPTOR = "generic";
private final AssetService assetService; private final AssetService assetService;
@@ -37,6 +38,23 @@ public class WebController {
return "index"; return "index";
} }
/**
* Handles the view of an asset by its QR code.
* If the asset does not exist, it redirects to the index page.
*
* @param qr The QR code of the asset to view.
*/
@GetMapping("/view/{qr}")
public String view(Model model, @PathVariable long qr) {
var asset = assetService.getAssetByQr(qr);
if (asset == null) {
return "redirect:/";
}
model.addAttribute("asset", asset);
model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(asset.getAsset().getType()));
return "view";
}
/** /**
* Shows a view where the user can create the type of asset to create. * Shows a view where the user can create the type of asset to create.
*/ */

View File

@@ -46,7 +46,7 @@ public class AssetDescriptor {
.displayName(assetInfo.displayName()) .displayName(assetInfo.displayName())
.visible(assetInfo.isVisible()) .visible(assetInfo.isVisible())
.properties(Arrays.stream(assetType.getDeclaredFields()) .properties(Arrays.stream(assetType.getDeclaredFields())
.map(AssetProperty::loadFrom) .map(field -> AssetProperty.loadFrom(field, assetInfo.type()))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toList()); .toList());
if (Asset.class.isAssignableFrom(assetType)) { if (Asset.class.isAssignableFrom(assetType)) {

View File

@@ -1,6 +1,9 @@
package be.seeseepuff.pcinv.meta; package be.seeseepuff.pcinv.meta;
import be.seeseepuff.pcinv.models.Asset;
import be.seeseepuff.pcinv.models.AssetCondition; import be.seeseepuff.pcinv.models.AssetCondition;
import be.seeseepuff.pcinv.models.GenericAsset;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@@ -10,6 +13,7 @@ import lombok.Singular;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function;
/** /**
* Represents a property of an asset, such as its name or type. * Represents a property of an asset, such as its name or type.
@@ -35,6 +39,8 @@ public class AssetProperty {
private final boolean capacityAsIEC; private final boolean capacityAsIEC;
/// A setter function that can be used to set the value of the property on an asset. /// A setter function that can be used to set the value of the property on an asset.
private final BiConsumer<Object, Object> setter; private final BiConsumer<Object, Object> setter;
/// A getter function that can be used to get the value of the property from an asset.
private final Function<Object, Object> getter;
/** /**
* Enum representing the possible types of asset properties. * Enum representing the possible types of asset properties.
@@ -66,7 +72,7 @@ public class AssetProperty {
* @return An AssetProperty instance with the name, display name, and type determined from the field. * @return An AssetProperty instance with the name, display name, and type determined from the field.
*/ */
@Nullable @Nullable
public static AssetProperty loadFrom(Field property) { public static AssetProperty loadFrom(Field property, @Nonnull String assetType) {
var annotation = property.getAnnotation(Property.class); var annotation = property.getAnnotation(Property.class);
if (annotation == null) { if (annotation == null) {
return null; return null;
@@ -85,6 +91,17 @@ public class AssetProperty {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
})
.getter(obj -> {
try {
if (assetType.equals(GenericAsset.TYPE) && obj instanceof Asset asset) {
obj = asset.getAsset();
}
property.setAccessible(true);
return property.get(obj);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}); });
if (type.isEnum) { if (type.isEnum) {
@@ -144,6 +161,39 @@ public class AssetProperty {
setter.accept(asset, value); setter.accept(asset, value);
} }
/**
* Gets the value of the property from the given asset.
*
* @param asset The asset to get the property value from.
* @return The value of the property.
*/
public Object getValue(Object asset) {
return getter.apply(asset);
}
/**
* Renders the value of the property as a string.
*
* @return The rendered value as a string.
*/
public String renderValue(Object asset) {
var value = getValue(asset);
if (value == null) {
return "Unknown";
} else if (type == Type.INTEGER || type == Type.STRING) {
return value.toString();
} else if (type == Type.CAPACITY) {
return String.format("%s bytes", value);
} else if (type.isEnum) {
if (value instanceof AssetEnum assetEnum) {
return assetEnum.getDisplayName();
}
throw new IllegalArgumentException("Expected value to be an instance of AssetEnum, but got: " + value.getClass().getName());
} else {
return value.toString();
}
}
@Override @Override
public String toString() { public String toString() {
var enumOptions = ""; var enumOptions = "";

View File

@@ -23,6 +23,8 @@ import lombok.Setter;
@Table(name = "assets") @Table(name = "assets")
public class GenericAsset public class GenericAsset
{ {
public static final String TYPE = "asset";
@Id @GeneratedValue @Id @GeneratedValue
private long id; private long id;
@@ -30,6 +32,9 @@ public class GenericAsset
@Property(value = "QR", required = true) @Property(value = "QR", required = true)
private long qr; private long qr;
/// The type of asset
private String type;
/// The brand of the asset. /// The brand of the asset.
@Property("Brand") @Property("Brand")
private String brand; private String brand;

View File

@@ -1,6 +1,7 @@
package be.seeseepuff.pcinv.repositories; package be.seeseepuff.pcinv.repositories;
import be.seeseepuff.pcinv.models.Asset; import be.seeseepuff.pcinv.models.Asset;
import be.seeseepuff.pcinv.models.GenericAsset;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
@@ -16,6 +17,8 @@ public interface AssetRepository<T extends Asset> {
} }
} }
T findByAsset(GenericAsset asset);
Class<T> getAssetType(); Class<T> getAssetType();
long count(); long count();

View File

@@ -5,4 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public interface GenericAssetRepository extends JpaRepository<GenericAsset, Long> { public interface GenericAssetRepository extends JpaRepository<GenericAsset, Long> {
GenericAsset findByQr(long qr);
} }

View File

@@ -35,6 +35,21 @@ public class AssetService {
return genericRepository.count(); return genericRepository.count();
} }
/**
* Retrieves an asset by its QR code.
*
* @param qr the QR code of the asset to retrieve
* @return the Asset associated with the given QR code
* @throws IllegalArgumentException if no asset is found with the given QR code
*/
public Asset getAssetByQr(long qr) {
var genericAsset = genericRepository.findByQr(qr);
if (genericAsset == null) {
throw new IllegalArgumentException("No asset found with QR code: " + qr);
}
return getRepositoryFor(genericAsset.getType()).findByAsset(genericAsset);
}
/** /**
* Retrieves the global asset descriptors for all asset types. * Retrieves the global asset descriptors for all asset types.
* *
@@ -69,10 +84,10 @@ public class AssetService {
* @return a list of AssetDescriptors for the specified type * @return a list of AssetDescriptors for the specified type
*/ */
public List<AssetDescriptor> getAssetDescriptorTree(String type) { public List<AssetDescriptor> getAssetDescriptorTree(String type) {
if (type.equals("asset")) { if (type.equals(GenericAsset.TYPE)) {
return List.of(getAssetDescriptor("asset")); return List.of(getAssetDescriptor(GenericAsset.TYPE));
} }
return List.of(getAssetDescriptor("asset"), getAssetDescriptor(type)); return List.of(getAssetDescriptor(GenericAsset.TYPE), getAssetDescriptor(type));
} }
/** /**
@@ -84,10 +99,11 @@ public class AssetService {
*/ */
@Transactional @Transactional
public Asset createAsset(String type, Map<String, String> formData) { public Asset createAsset(String type, Map<String, String> formData) {
var genericDescriptor = getAssetDescriptor("asset"); var genericDescriptor = getAssetDescriptor(GenericAsset.TYPE);
var assetDescriptor = getAssetDescriptor(type); var assetDescriptor = getAssetDescriptor(type);
var genericAsset = new GenericAsset(); var genericAsset = new GenericAsset();
genericAsset.setType(type);
fillIn(genericAsset, genericDescriptor, formData); fillIn(genericAsset, genericDescriptor, formData);
var asset = assetDescriptor.newInstance(); var asset = assetDescriptor.newInstance();

View File

@@ -4,7 +4,12 @@
<title>PC Inventory</title> <title>PC Inventory</title>
</head> </head>
<body> <body>
<h1>PC Inventory - <span th:text="${title}"></span></h1> <h1>PC Inventory - <span th:text="${title}"></span>
</h1>
<hr>
<a href="/">Home</a>
<a href="/browse">Browse</a>
<a href="/create">Create</a>
<hr> <hr>
<div th:replace="${content}"> <div th:replace="${content}">
</div> </div>

View File

@@ -1,6 +1,5 @@
<div th:replace="fragments :: base(title='Home', content=~{::content})"> <div th:replace="fragments :: base(title='Home', content=~{::content})">
<div th:fragment="content"> <div th:fragment="content">
<a href="/create">Create a new device</a>
<p>This system holds <span th:text="${asset_count}">5</span> assets.</p> <p>This system holds <span th:text="${asset_count}">5</span> assets.</p>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,14 @@
<body th:replace="~{fragments :: base(title='Select type to create', content=~{::content})}">
<div th:fragment="content">
View device details
<div th:each="d : ${descriptors}">
<h2 th:text="${d.displayName}"></h2>
<table border="1">
<tr th:each="p : ${d.properties}">
<td><b th:text="${p.displayName}"></b></td>
<td th:text="${p.renderValue(asset)}"></td>
</tr>
</table>
</div>
</div>
</body>