Files
pcinv/src/main/java/be/seeseepuff/pcinv/services/AssetService.java

297 lines
11 KiB
Java

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.*;
/**
* Service for managing assets in the repository.
* Provides methods to interact with the asset repositories.
*/
@Service
@RequiredArgsConstructor
public class AssetService {
private final GenericAssetRepository genericRepository;
private final Collection<AssetRepository<?>> repositories;
/**
* Returns the count of all assets in the repository.
*
* @return the total number of assets
*/
public long countAssets() {
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 all assets of a specific type.
*
* @param type the type of asset to retrieve
* @return a list of assets of the specified type
*/
public List<? extends Asset> getAssetsByType(String type) {
return getRepositoryFor(type).findAll();
}
/**
* Retrieves the global asset descriptors for all asset types.
*
* @return the AssetProperties for the specified type
*/
public AssetDescriptors getAssetDescriptors() {
var descriptors = new AssetDescriptors();
descriptors.loadFrom(GenericAsset.class);
for (var repository : repositories) {
descriptors.loadFrom(repository.getAssetType());
}
return descriptors;
}
/**
* Retrieves the asset properties for a specific type.
*
* @param type the type of asset to retrieve properties for
* @return the AssetProperties for the specified type
*/
public AssetDescriptor getAssetDescriptor(String type) {
return getAssetDescriptors().getAssets().stream()
.filter(asset -> asset.getType().equals(type))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown asset type: " + type));
}
/**
* Retrieves a tree of asset descriptors for the specified type.
*
* @param type the type of asset to retrieve descriptors for
* @return a list of AssetDescriptors for the specified type
*/
public List<AssetDescriptor> getAssetDescriptorTree(String type) {
if (type.equals(GenericAsset.TYPE)) {
return List.of(getAssetDescriptor(GenericAsset.TYPE));
}
return List.of(getAssetDescriptor(GenericAsset.TYPE), 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(GenericAsset.TYPE);
var assetDescriptor = getAssetDescriptor(type);
var genericAsset = new GenericAsset();
genericAsset.setType(type);
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;
}
/**
* Edits an existing asset with the provided form data.
*
* @param qr The QR code of the asset to edit.
* @param formData The form data containing the updated asset properties.
* @return The updated asset.
*/
@Transactional
public Asset editAsset(long qr, Map<String, String> formData) {
var genericAsset = genericRepository.findByQr(qr);
if (genericAsset == null) {
throw new IllegalArgumentException("No asset found with QR code: " + qr);
}
var assetType = genericAsset.getType();
var assetDescriptor = getAssetDescriptor(assetType);
var asset = getRepositoryFor(assetType).findByAsset(genericAsset);
fillIn(genericAsset, getAssetDescriptor(GenericAsset.TYPE), formData);
fillIn(asset, assetDescriptor, formData);
genericRepository.saveAndFlush(genericAsset);
return getRepositoryFor(assetType).saveAndFlushAsset(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 (property.isInputList()) {
var selectedItem = formData.get(assetDescriptor.asString(property) + "-list");
if (selectedItem != null && !selectedItem.isBlank() && !selectedItem.equals("__new__")) {
value = selectedItem;
}
}
if (value == null && property.isRequired()) {
throw new IllegalArgumentException("Property '" + property.getName() + "' is required but not provided.");
}
property.setValue(asset, value);
}
}
/**
* 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() == AssetProperty.Type.BOOLEAN) {
return switch (stringValue.toLowerCase()) {
case "true" -> true;
case "false" -> false;
default -> null;
};
} 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());
}
}
@Transactional
public void deleteAsset(long qr) {
var genericAsset = genericRepository.findByQr(qr);
if (genericAsset == null) {
throw new IllegalArgumentException("No asset found with QR code: " + qr);
}
var assetType = genericAsset.getType();
var assetRepository = getRepositoryFor(assetType);
assetRepository.deleteByAsset(genericAsset);
assetRepository.flush();
genericRepository.delete(genericAsset);
genericRepository.flush();
}
/**
* Retrieves the input list mapping for a specific asset type.
*
* @param type the type of asset to retrieve the input list for
* @return a map of input names to their corresponding list
*/
public Map<String, Set<String>> getInputList(String type) {
var map = new HashMap<String, Set<String>>();
var tree = getAssetDescriptorTree(type);
for (var descriptor : tree) {
for (var property : descriptor.getProperties()) {
if (property.isInputList()) {
var inputList = getInputList(descriptor, property);
map.put(descriptor.asString(property), inputList);
}
}
}
return map;
}
/**
* Retrieves the input list for a specific asset descriptor and property.
*
* @param descriptor the asset descriptor containing the property
* @param property the asset property to retrieve the input list for
* @return a set of input values for the specified property
*/
private Set<String> getInputList(AssetDescriptor descriptor, AssetProperty property) {
List<?> entries;
if (descriptor.getType().equals(GenericAsset.TYPE)) {
entries = genericRepository.findAll();
} else {
var repository = getRepositoryFor(descriptor.getType());
entries = repository.findAll();
}
Set<String> inputList = new TreeSet<>();
for (var entry : entries) {
var value = property.getValue(entry);
if (value != null) {
inputList.add(value.toString());
}
}
return inputList;
}
}