Actually support creating assets
This commit is contained in:
@@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Controller for handling web requests related to assets.
|
||||
* Provides endpoints for viewing and creating assets.
|
||||
@@ -68,8 +70,13 @@ public class WebController {
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
|
||||
)
|
||||
public String createTypePost(Model model, @PathVariable String type, @RequestBody MultiValueMap<String, String> formData) {
|
||||
model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type));
|
||||
model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type));
|
||||
return "create_asset";
|
||||
var formMap = new HashMap<String, String>();
|
||||
formData.forEach((key, values) -> {
|
||||
if (!values.isEmpty()) {
|
||||
formMap.put(key, values.getFirst());
|
||||
}
|
||||
});
|
||||
var asset = assetService.createAsset(type, formMap);
|
||||
return "redirect:/view/" + asset.getQr();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package be.seeseepuff.pcinv.meta;
|
||||
|
||||
import be.seeseepuff.pcinv.models.Asset;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Describes the properties of an asset
|
||||
@@ -26,6 +29,9 @@ public class AssetDescriptor {
|
||||
@lombok.Singular
|
||||
private Collection<AssetProperty> properties;
|
||||
|
||||
/// A supplier that can be used to create a new instance of the asset type described by this descriptor.
|
||||
private Supplier<Asset> instanceProducer;
|
||||
|
||||
/**
|
||||
* Loads the asset properties from a given asset class.
|
||||
*
|
||||
@@ -35,15 +41,25 @@ public class AssetDescriptor {
|
||||
public static AssetDescriptor loadFrom(Class<?> assetType) {
|
||||
var assetInfo = assetType.getAnnotation(AssetInfo.class);
|
||||
Objects.requireNonNull(assetInfo, "Asset class must be annotated with @AssetInfo");
|
||||
return AssetDescriptor.builder()
|
||||
var builder = AssetDescriptor.builder()
|
||||
.type(assetInfo.type())
|
||||
.displayName(assetInfo.displayName())
|
||||
.visible(assetInfo.isVisible())
|
||||
.properties(Arrays.stream(assetType.getDeclaredFields())
|
||||
.map(AssetProperty::loadFrom)
|
||||
.filter(Objects::nonNull)
|
||||
.toList())
|
||||
.build();
|
||||
.toList());
|
||||
if (Asset.class.isAssignableFrom(assetType)) {
|
||||
builder.instanceProducer(() -> {
|
||||
try {
|
||||
return (Asset) assetType.getConstructor().newInstance();
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
|
||||
InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,4 +94,16 @@ public class AssetDescriptor {
|
||||
public String asString(AssetProperty property) {
|
||||
return String.format("%s-%s", type, property.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the asset type described by this descriptor.
|
||||
*
|
||||
* @return A new instance of the asset type.
|
||||
*/
|
||||
public Asset newInstance() {
|
||||
if (instanceProducer == null) {
|
||||
throw new IllegalStateException("Instance producer is not set for asset descriptor: " + type);
|
||||
}
|
||||
return instanceProducer.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class AssetOption {
|
||||
private final String value;
|
||||
/// The display name of the option.
|
||||
private final String displayName;
|
||||
/// The actual enum value associated with this option.
|
||||
private final Object enumConstant;
|
||||
/// Whether this option is the default value for the property.
|
||||
private final boolean isDefaultValue;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.Singular;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Represents a property of an asset, such as its name or type.
|
||||
@@ -32,6 +33,8 @@ public class AssetProperty {
|
||||
private final boolean capacityAsSI;
|
||||
/// Whether the capacity should be displayed in IEC units (e.g., GiB, MiB).
|
||||
private final boolean capacityAsIEC;
|
||||
/// A setter function that can be used to set the value of the property on an asset.
|
||||
private final BiConsumer<Object, Object> setter;
|
||||
|
||||
/**
|
||||
* Enum representing the possible types of asset properties.
|
||||
@@ -74,7 +77,15 @@ public class AssetProperty {
|
||||
.name(property.getName())
|
||||
.displayName(annotation.value())
|
||||
.type(type)
|
||||
.required(annotation.required());
|
||||
.required(annotation.required())
|
||||
.setter((obj, value) -> {
|
||||
try {
|
||||
property.setAccessible(true);
|
||||
property.set(obj, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
if (type.isEnum) {
|
||||
var enumConstants = property.getType().getEnumConstants();
|
||||
@@ -86,6 +97,7 @@ public class AssetProperty {
|
||||
.value(assetEnum.getValue())
|
||||
.displayName(assetEnum.getDisplayName())
|
||||
.isDefaultValue(assetEnum.isDefaultValue())
|
||||
.enumConstant(enumConstant)
|
||||
.build();
|
||||
builder.option(option);
|
||||
}
|
||||
@@ -119,6 +131,19 @@ public class AssetProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the property on the given asset.
|
||||
*
|
||||
* @param asset The asset to set the property on.
|
||||
* @param value The value to set for the property.
|
||||
*/
|
||||
public void setValue(Object asset, Object value) {
|
||||
if (value == null && required) {
|
||||
throw new IllegalArgumentException("Property '" + name + "' is required but received null value.");
|
||||
}
|
||||
setter.accept(asset, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var enumOptions = "";
|
||||
|
||||
@@ -5,5 +5,9 @@ public interface Asset {
|
||||
|
||||
GenericAsset getAsset();
|
||||
|
||||
default long getQr() {
|
||||
return getAsset().getQr();
|
||||
}
|
||||
|
||||
void setAsset(GenericAsset asset);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class HddAsset implements Asset
|
||||
/// The capacity of the drive in bytes.
|
||||
@Property("Capacity")
|
||||
@Capacity(si = true, iec = true)
|
||||
private long capacity;
|
||||
private Long capacity;
|
||||
|
||||
/// The drive's interface type, such as SATA, IDE, ISA-16, ...
|
||||
@Property("Interface Type")
|
||||
|
||||
@@ -31,7 +31,7 @@ public class RamAsset implements Asset
|
||||
/// The capacity of the RAM in bytes.
|
||||
@Property("Capacity")
|
||||
@Capacity
|
||||
private long capacity;
|
||||
private Long capacity;
|
||||
|
||||
/// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc...
|
||||
@Property("Type")
|
||||
|
||||
@@ -5,6 +5,17 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AssetRepository<T extends Asset> {
|
||||
T saveAndFlush(T entity);
|
||||
|
||||
default Asset saveAndFlushAsset(Asset entity) {
|
||||
if (getAssetType().isInstance(entity)) {
|
||||
//noinspection unchecked
|
||||
return saveAndFlush((T) entity);
|
||||
} else {
|
||||
throw new ClassCastException("Entity is not of type " + getAssetType().getName());
|
||||
}
|
||||
}
|
||||
|
||||
Class<T> getAssetType();
|
||||
|
||||
long count();
|
||||
|
||||
@@ -2,14 +2,19 @@ package be.seeseepuff.pcinv.services;
|
||||
|
||||
import be.seeseepuff.pcinv.meta.AssetDescriptor;
|
||||
import be.seeseepuff.pcinv.meta.AssetDescriptors;
|
||||
import be.seeseepuff.pcinv.meta.AssetInfo;
|
||||
import be.seeseepuff.pcinv.meta.AssetProperty;
|
||||
import be.seeseepuff.pcinv.models.Asset;
|
||||
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 org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Service for managing assets in the repository.
|
||||
@@ -18,7 +23,7 @@ import java.util.List;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AssetService {
|
||||
private final GenericAssetRepository assetRepository;
|
||||
private final GenericAssetRepository genericRepository;
|
||||
private final Collection<AssetRepository<?>> repositories;
|
||||
|
||||
/**
|
||||
@@ -27,7 +32,7 @@ public class AssetService {
|
||||
* @return the total number of assets
|
||||
*/
|
||||
public long countAssets() {
|
||||
return assetRepository.count();
|
||||
return genericRepository.count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,4 +74,103 @@ public class AssetService {
|
||||
}
|
||||
return List.of(getAssetDescriptor("asset"), getAssetDescriptor(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new asset of the specified type with the provided form data.
|
||||
*
|
||||
* @param type The type of asset to create.
|
||||
* @param formData The form data containing the asset properties.
|
||||
* @return The created asset.
|
||||
*/
|
||||
@Transactional
|
||||
public Asset createAsset(String type, Map<String, String> formData) {
|
||||
var genericDescriptor = getAssetDescriptor("asset");
|
||||
var assetDescriptor = getAssetDescriptor(type);
|
||||
|
||||
var genericAsset = new GenericAsset();
|
||||
fillIn(genericAsset, genericDescriptor, formData);
|
||||
|
||||
var asset = assetDescriptor.newInstance();
|
||||
fillIn(asset, assetDescriptor, formData);
|
||||
|
||||
genericAsset = genericRepository.saveAndFlush(genericAsset);
|
||||
asset.setAsset(genericAsset);
|
||||
asset = getRepositoryFor(type).saveAndFlushAsset(asset);
|
||||
return asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the asset repository for the specified type.
|
||||
*
|
||||
* @param type the type of asset to get the repository for
|
||||
* @return the AssetRepository for the specified type
|
||||
*/
|
||||
private AssetRepository<?> getRepositoryFor(String type) {
|
||||
return repositories.stream()
|
||||
.filter(repo -> repo.getAssetType().getAnnotation(AssetInfo.class).type().equals(type))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No repository found for type: " + type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the properties of a generic asset from the form data.
|
||||
*
|
||||
* @param asset The generic asset to fill in.
|
||||
* @param assetDescriptor The descriptor containing the properties to fill in.
|
||||
* @param formData The form data containing the property values.
|
||||
*/
|
||||
private void fillIn(Object asset, AssetDescriptor assetDescriptor, Map<String, String> formData) {
|
||||
for (var property : assetDescriptor.getProperties()) {
|
||||
var value = parseValue(assetDescriptor, property, formData);
|
||||
if (value != null) {
|
||||
property.setValue(asset, value);
|
||||
} else if (property.isRequired()) {
|
||||
throw new IllegalArgumentException("Property '" + property.getName() + "' is required but not provided.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string value into the appropriate type based on the asset property.
|
||||
*
|
||||
* @param descriptor The asset descriptor containing the property.
|
||||
* @param property The asset property to determine the type.
|
||||
* @param values The map of values from the form data.
|
||||
* @return The parsed value as an Object.
|
||||
*/
|
||||
private Object parseValue(AssetDescriptor descriptor, AssetProperty property, Map<String, String> values) {
|
||||
if (property.getType() == AssetProperty.Type.CAPACITY) {
|
||||
var value = values.get(descriptor.asString(property) + "-value");
|
||||
var unit = values.get(descriptor.asString(property) + "-unit");
|
||||
if (value == null || value.isBlank() || unit == null || unit.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(value) * Long.parseLong(unit);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid numeric value for property '" + property.getName() + "': " + value, e);
|
||||
}
|
||||
}
|
||||
|
||||
var stringValue = values.get(descriptor.asString(property));
|
||||
if (stringValue == null || stringValue.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (property.getType() == AssetProperty.Type.INTEGER) {
|
||||
return Integer.parseInt(stringValue);
|
||||
} else if (property.getType() == AssetProperty.Type.STRING) {
|
||||
return stringValue;
|
||||
} else if (property.getType().isEnum) {
|
||||
for (var option : property.getOptions()) {
|
||||
if (option.getValue().equals(stringValue)) {
|
||||
return option.getEnumConstant();
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid value for enum property '" + property.getName() + "': " + stringValue);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported property type: " + property.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user