From cc41f564b095d7772a6207e9f7a72927616e318e Mon Sep 17 00:00:00 2001 From: Sikandar Ejaz <34721766+SikandarEjaz@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:48:38 -0400 Subject: [PATCH 1/3] code refactor --- .../implementations}/CSVProducer.java | 3 +-- .../implementations}/JSONProducer.java | 3 +-- .../producers/CKANMetadataProducer.java | 2 +- .../citydata/producers/GeometryProducer.java | 11 +++++++---- .../producers/RetrofitResultsProducer.java | 2 +- .../test/producers/BaseProducersTest.java | 4 ++-- .../test/producers/GeometryProducerTest.java | 18 +++++++----------- 7 files changed, 20 insertions(+), 23 deletions(-) rename Middleware/src/main/java/ca/concordia/encs/citydata/{producers/base => core/implementations}/CSVProducer.java (91%) rename Middleware/src/main/java/ca/concordia/encs/citydata/{producers/base => core/implementations}/JSONProducer.java (91%) diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/CSVProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java similarity index 91% rename from Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/CSVProducer.java rename to Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java index 9caa134b..82b3f194 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/CSVProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java @@ -1,4 +1,4 @@ -package ca.concordia.encs.citydata.producers.base; +package ca.concordia.encs.citydata.core.implementations; import java.io.IOException; import java.io.OutputStream; @@ -8,7 +8,6 @@ import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; /** diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/JSONProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java similarity index 91% rename from Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/JSONProducer.java rename to Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java index 8f4ffd17..92436b53 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/base/JSONProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java @@ -1,4 +1,4 @@ -package ca.concordia.encs.citydata.producers.base; +package ca.concordia.encs.citydata.core.implementations; import java.io.OutputStream; import java.util.ArrayList; @@ -8,7 +8,6 @@ import com.google.gson.JsonParser; import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; /** diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java index e5b37a23..b470b3c9 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java @@ -8,8 +8,8 @@ import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; -import ca.concordia.encs.citydata.producers.base.JSONProducer; /** * This producer can connect to a CKAN instance and fetch either dataset or resource metadata. diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java index 32e6a64d..9e8a17c4 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java @@ -3,10 +3,9 @@ import java.security.InvalidParameterException; import ca.concordia.encs.citydata.core.contracts.IOperation; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; -import ca.concordia.encs.citydata.producers.base.JSONProducer; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; +import ca.concordia.encs.citydata.core.utils.RequestOptions; /** * This Producer outputs GeoJSON geometries for a given city. @@ -15,7 +14,11 @@ * @since 2025-05-28 */ -public class GeometryProducer extends AbstractProducer implements IProducer { +public class GeometryProducer extends JSONProducer { + public GeometryProducer(String filePath, RequestOptions fileOptions) { + super(filePath, fileOptions); + } + private JSONProducer jsonProducer; public void setCity(String city) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java index 879bad9b..76545d93 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java @@ -10,10 +10,10 @@ import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; import ca.concordia.encs.citydata.core.utils.StringUtils; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; -import ca.concordia.encs.citydata.producers.base.JSONProducer; import ca.concordia.encs.citydata.runners.SingleStepRunner; /** diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/BaseProducersTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/BaseProducersTest.java index e56f5f1a..77acd3bd 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/BaseProducersTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/BaseProducersTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.api.Test; import ca.concordia.encs.citydata.core.contracts.IProducer; +import ca.concordia.encs.citydata.core.implementations.CSVProducer; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; -import ca.concordia.encs.citydata.producers.base.CSVProducer; -import ca.concordia.encs.citydata.producers.base.JSONProducer; import ca.concordia.encs.citydata.runners.SingleStepRunner; /** diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java index b84d3bbe..fe69451b 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java @@ -5,7 +5,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -13,9 +12,6 @@ import org.springframework.http.MediaType; import ca.concordia.encs.citydata.core.configs.AppConfig; -import ca.concordia.encs.citydata.operations.MergeOperation; -import ca.concordia.encs.citydata.producers.EnergyConsumptionProducer; -import ca.concordia.encs.citydata.producers.GeometryProducer; import ca.concordia.encs.citydata.test.AbstractTest; import ca.concordia.encs.citydata.test.PayloadFactory; @@ -32,14 +28,14 @@ public class GeometryProducerTest extends AbstractTest { - private final String CITY = "montreal"; + // private final String CITY = "montreal"; - @BeforeEach - void setUp() { - GeometryProducer geometryProducer = new GeometryProducer(); - EnergyConsumptionProducer energyConsumptionProducer = new EnergyConsumptionProducer(); - MergeOperation mergeOperation = new MergeOperation(); - } + /* @BeforeEach + void setUp() { + GeometryProducer geometryProducer = new GeometryProducer(); + EnergyConsumptionProducer energyConsumptionProducer = new EnergyConsumptionProducer(); + MergeOperation mergeOperation = new MergeOperation(); + }*/ @Test public void testMergeOperationViaAPI() throws Exception { From 4a68f79468c5278855fabe9161ce454b6cb259f3 Mon Sep 17 00:00:00 2001 From: Sikandar Ejaz <34721766+SikandarEjaz@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:04:48 -0400 Subject: [PATCH 2/3] addressed issue #222 --- Middleware/pom.xml | 8 +- .../encs/citydata/core/Application.java | 3 +- .../core/exceptions/MiddlewareException.java | 9 +- .../implementations/AbstractProducer.java | 21 ++-- .../core/implementations/CSVProducer.java | 10 +- .../core/implementations/JSONProducer.java | 11 +- .../citydata/core/utils/ReflectionUtils.java | 28 ++++- .../citydata/producers/BuildingProducer.java | 15 ++- .../producers/CKANMetadataProducer.java | 13 ++- .../encs/citydata/producers/CKANProducer.java | 10 +- .../producers/CalibrationProducer.java | 45 ++++---- .../producers/EnergyConsumptionProducer.java | 20 +++- .../EnvironmentalSensorProducer.java | 10 +- .../citydata/producers/ExceptionProducer.java | 1 + .../producers/RandomNumberProducer.java | 53 --------- .../producers/RandomStringProducer.java | 44 -------- .../producers/RoomOccupancyProducer.java | 10 +- .../citydata/runners/SequentialRunner.java | 14 +-- .../encs/citydata/test/PayloadFactory.java | 4 +- .../encs/citydata/test/core/ApplyTest.java | 102 +----------------- .../encs/citydata/test/core/ExistsTest.java | 50 --------- .../test/operations/MergeOperationTests.java | 14 +-- .../EnergyConsumptionWithFilterTest.java | 20 ++-- .../test/producers/GeometryProducerTest.java | 38 ++++--- .../test/producers/ProducersSanityTest.java | 83 ++++++-------- .../test/producers/ProducersTests.java | 76 ------------- .../test/producers/RetrofitResultsTest.java | 6 +- .../test/utils/ReflectionUtilsTest.java | 2 +- 28 files changed, 225 insertions(+), 495 deletions(-) delete mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomNumberProducer.java delete mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomStringProducer.java delete mode 100644 Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersTests.java diff --git a/Middleware/pom.xml b/Middleware/pom.xml index cf54c9e6..94e3ca96 100644 --- a/Middleware/pom.xml +++ b/Middleware/pom.xml @@ -95,11 +95,6 @@ org.springframework.boot spring-boot-starter-oauth2-resource-server - - org.springframework.boot - spring-boot-starter-test - test - org.springframework.security spring-security-test @@ -118,6 +113,9 @@ src/main/resources + + src/test/resources + ${project.basedir} diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/Application.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/Application.java index 8e67c199..f2e1c503 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/Application.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/Application.java @@ -18,12 +18,11 @@ * @since 2025-01-01 */ - @SpringBootApplication @ComponentScan(basePackages = { "ca.concordia.encs.citydata.core.controllers", "ca.concordia.encs.citydata.core.configs", "ca.concordia.encs.citydata.datastores", "ca.concordia.encs.citydata.services", "ca.concordia.encs.citydata.core.utils", - "ca.concordia.encs.citydata.core" }) + "ca.concordia.encs.citydata.core", "ca.concordia.encs.citydata.producers" }) @EnableConfigurationProperties(RsaKeyProperties.class) public class Application { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/exceptions/MiddlewareException.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/exceptions/MiddlewareException.java index 2da0b422..e3d3813d 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/exceptions/MiddlewareException.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/exceptions/MiddlewareException.java @@ -7,15 +7,18 @@ */ public class MiddlewareException extends RuntimeException { - public MiddlewareException(String message) { super(message); } + public MiddlewareException(final String message, final Exception cause) { + super(message, cause); + } + public static class InvalidProducerException extends MiddlewareException { - public InvalidProducerException(String producerName) { + public InvalidProducerException(String producerName, final Exception e) { super("Producer " + producerName - + " was not found. Please check whether the fully-qualified name is correct and try again."); + + " was not found. Please check whether the fully-qualified name is correct and try again.", e); } } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java index 04a550fa..ff028252 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java @@ -39,13 +39,22 @@ */ public abstract class AbstractProducer extends AbstractEntity implements IProducer { - private String filePath; private RequestOptions fileOptions; private IOperation operation; private final Set runners = new HashSet<>(); private ArrayList result = new ArrayList<>(); + public AbstractProducer(final String filePath, final RequestOptions fileOptions) { + this.filePath = filePath; + this.fileOptions = fileOptions; + this.setMetadata("role", "producer"); + } + + public AbstractProducer(final String filePath) { + this(filePath, null); + } + public String getFilePath() { return filePath; } @@ -74,6 +83,11 @@ public void setResult(ArrayList result) { this.result = result; } + @Override + public ArrayList getResult() { + return this.result; + } + public AbstractProducer() { this.setMetadata("role", "producer"); } @@ -113,11 +127,6 @@ public void applyOperation() { this.notifyObservers(); } - @Override - public ArrayList getResult() { - return this.result; - } - public boolean isEmpty() { return this.result == null || this.result.isEmpty(); } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java index 82b3f194..626e0d21 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java @@ -19,16 +19,16 @@ public class CSVProducer extends AbstractProducer implements IProducer { - public CSVProducer(String filePath, RequestOptions fileOptions) { - this.setFilePath(filePath); - this.setFileOptions(fileOptions); + public CSVProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); } - // I added the error handling to ensure I actually read my local file + public CSVProducer(final String filePath) { + super(filePath); + } @Override public void fetch() { - try (OutputStream outputStream = this.fetchFromPath(); OutputStreamWriter writer = new OutputStreamWriter(outputStream)) { writer.flush(); // Ensure all data is written to the stream diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java index 92436b53..514e4611 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java @@ -19,11 +19,14 @@ public class JSONProducer extends AbstractProducer implements IProducer { - public JSONProducer(String filePath, RequestOptions fileOptions) { - this.setFilePath(filePath); - this.setFileOptions(fileOptions); + public JSONProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); } - + + public JSONProducer(final String filePath) { + super(filePath); + } + @Override public void fetch() { final ArrayList jsonOutput = new ArrayList<>(); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/utils/ReflectionUtils.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/utils/ReflectionUtils.java index 89c29ff3..333f7a51 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/utils/ReflectionUtils.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/utils/ReflectionUtils.java @@ -31,7 +31,7 @@ public static JsonElement getRequiredField(JsonObject jsonObject, String fieldNa return jsonObject.get(fieldName); } - public static Object instantiateClass(String className) throws MiddlewareException { + public static Object instantiateOperation(final String className) throws MiddlewareException { try { final Class clazz = Class.forName(className); return clazz.getDeclaredConstructor().newInstance(); @@ -39,7 +39,7 @@ public static Object instantiateClass(String className) throws MiddlewareExcepti if (className.contains("Operation") || className.contains("operation")) { throw new InvalidOperationException(className); } else if (className.contains("Producer") || className.contains("producer")) { - throw new InvalidProducerException(className); + throw new InvalidProducerException(className, e); } else { throw new MiddlewareException("Producer or Operation " + e.getClass().getSimpleName() + " was not found. Please check for typos and try again."); @@ -50,6 +50,30 @@ public static Object instantiateClass(String className) throws MiddlewareExcepti } } + public static Object instantiateProducer(final String producerClassName, final String filePath) + throws MiddlewareException { + + return ReflectionUtils.instantiateProducer(producerClassName, filePath, null); + } + + public static Object instantiateProducer(final String producerClassName, final String filePath, + final RequestOptions options) throws MiddlewareException { + try { + final Class clazz = Class.forName(producerClassName); + return clazz.getDeclaredConstructor(String.class, RequestOptions.class).newInstance(filePath, options); + } catch (ClassNotFoundException | NoSuchMethodException e) { + if (producerClassName.contains("Producer") || producerClassName.contains("producer")) { + throw new InvalidProducerException(producerClassName, e); + } else { + throw new MiddlewareException( + "Producer " + producerClassName + " was not found. Please check for typos and try again."); + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new MiddlewareException("CITYdata entity could not be created: " + producerClassName + + ". Please contact the system administrator."); + } + } + public static void setParameters(Object instance, JsonArray params) throws MiddlewareException { int i = 0; JsonElement paramValue = new JsonObject(); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java index 3c32d91f..f6e3102c 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java @@ -12,8 +12,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; +import ca.concordia.encs.citydata.core.utils.RequestOptions; /** * This Producer outputs metadata about a building, such as floors, zones and sensors. @@ -21,10 +21,19 @@ * @since 2025-05-28 */ -public class BuildingProducer extends AbstractProducer implements IProducer { +// public class BuildingProducer extends AbstractProducer implements IProducer { +public class BuildingProducer extends JSONProducer { private String filePath; + public BuildingProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); + } + + public BuildingProducer(final String filePath) { + super(filePath); + } + // Setter — for JSON files public void setBuildingName(String buildingName) { if (buildingName != null) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java index b470b3c9..abcde4f9 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java @@ -5,9 +5,7 @@ import com.google.gson.JsonObject; import ca.concordia.encs.citydata.core.contracts.IOperation; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; @@ -17,7 +15,8 @@ * @since 2025-02-12 */ -public class CKANMetadataProducer extends AbstractProducer implements IProducer { +//public class CKANMetadataProducer extends AbstractProducer implements IProducer { +public class CKANMetadataProducer extends JSONProducer { private String url; private String resourceId; @@ -25,6 +24,14 @@ public class CKANMetadataProducer extends AbstractProducer implement private IOperation jsonProducerOperation; private IRunner runnerObserver; + public CKANMetadataProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); + } + + public CKANMetadataProducer(final String filePath) { + super(filePath); + } + public void setUrl(String url) { if (url != null) { if (url.contains("http")) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java index 406727e9..703d7b81 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java @@ -31,6 +31,14 @@ public class CKANProducer extends AbstractProducer implements IProducer< private final DiskDatastore diskStore = DiskDatastore.getInstance(); private final ArrayList intermediateResult = new ArrayList<>(); + public CKANProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); + } + + public CKANProducer(final String filePath) { + super(filePath); + } + public void setUrl(String url) { if (url != null) { if (url.contains("http")) { @@ -83,7 +91,7 @@ private boolean isFileSupported(String mimetype) { private OutputStream fetchFromCkan() { try { // fetch resource metadata first - final CKANMetadataProducer metadataProducer = new CKANMetadataProducer(); + final CKANMetadataProducer metadataProducer = new CKANMetadataProducer(resourceId); metadataProducer.setUrl(this.url); metadataProducer.setResourceId(this.resourceId); final SingleStepRunner deckard = new SingleStepRunner(metadataProducer); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java index 38e9d898..7495654a 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java @@ -1,33 +1,34 @@ package ca.concordia.encs.citydata.producers; -import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Arrays; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.CSVProducer; -public class CalibrationProducer extends AbstractProducer implements IProducer { +//public class CalibrationProducer extends AbstractProducer implements IProducer { +public class CalibrationProducer extends CSVProducer { -// public void setFilePath(String fileName) { -// super.setFilePath("docs/examples/data/" + fileName); -// } - + /* public void setFilePath(String fileName) { + super.setFilePath("docs/examples/data/" + fileName); + }*/ - @Override - public void fetch() { - System.out.println("Fetching file from path: " + this.getFilePath()); - try { - OutputStream outputStream = this.fetchFromPath(); - String csvString = outputStream.toString(); - ArrayList csvLines = new ArrayList<>(Arrays.asList(csvString.split(System.lineSeparator()))); - this.setResult(csvLines); - this.applyOperation(); - } catch (Exception e) { - throw new MiddlewareException.DatasetNotFound("Error processing CSV data: " + e.getMessage()); - } - } + public CalibrationProducer(String filePath) { + super(filePath); + } + + @Override + public void fetch() { + System.out.println("Fetching file from path: " + this.getFilePath()); + try { + OutputStream outputStream = this.fetchFromPath(); + String csvString = outputStream.toString(); + ArrayList csvLines = new ArrayList<>(Arrays.asList(csvString.split(System.lineSeparator()))); + this.setResult(csvLines); + this.applyOperation(); + } catch (Exception e) { + throw new MiddlewareException.DatasetNotFound("Error processing CSV data: " + e.getMessage()); + } + } } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java index 84fbcc1b..af4640bc 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java @@ -14,10 +14,10 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException.DatasetNotFound; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.CSVProducer; +import ca.concordia.encs.citydata.core.utils.RequestOptions; import ca.concordia.encs.citydata.core.utils.StringUtils; /** @@ -30,7 +30,17 @@ * @since 2025-05-28 */ -public class EnergyConsumptionProducer extends AbstractProducer implements IProducer { +//public class EnergyConsumptionProducer extends AbstractProducer implements IProducer { +public class EnergyConsumptionProducer extends CSVProducer { + + public EnergyConsumptionProducer(String filePath) { + super(filePath); + } + + public EnergyConsumptionProducer(final String filePath, final RequestOptions fileOptions) { + super(filePath, fileOptions); + } + private String city; private String startDatetime; private String endDatetime; @@ -148,8 +158,8 @@ public void fetch() { resultRow.addProperty("error", e.getMessage()); resultsArray.add(resultRow); } finally { - final ArrayList updatedResult = this.getResult(); - updatedResult.add(resultsArray); + final ArrayList updatedResult = this.getResult(); + updatedResult.add(resultsArray.toString()); this.setResult(updatedResult); this.applyOperation(); } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java index 98c3c73d..d8ab0f41 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java @@ -4,9 +4,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.CSVProducer; /** * This producer reads environmental sensor data from a CSV source, processes it line by line, and produces a list of sensor @@ -16,10 +15,11 @@ * @date: 2025-10-03 */ -public class EnvironmentalSensorProducer extends AbstractProducer implements IProducer { - - public EnvironmentalSensorProducer() { +//public class EnvironmentalSensorProducer extends AbstractProducer implements IProducer { +public class EnvironmentalSensorProducer extends CSVProducer { + public EnvironmentalSensorProducer(String filePath) { + super(filePath); } @Override diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java index 4cb0c1f2..7d1aae04 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java @@ -19,6 +19,7 @@ public class ExceptionProducer extends AbstractProducer implements IProd public ExceptionProducer(Exception e) { final ArrayList result = new ArrayList<>(); result.add(e.getMessage()); + result.add("caused by: " + e.getCause()); this.setResult(result); } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomNumberProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomNumberProducer.java deleted file mode 100644 index ee79153c..00000000 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomNumberProducer.java +++ /dev/null @@ -1,53 +0,0 @@ -package ca.concordia.encs.citydata.producers; - -import java.util.ArrayList; -import java.util.Random; - -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; - -/** - * - * This Producer outputs random integers. For test only. - * - * @author Gabriel C. Ullmann - * @since 2025-05-28 - */ - -public class RandomNumberProducer extends AbstractProducer implements IProducer { - private int listSize; - private int generationDelay; - - public void setListSize(int listSize) { - this.listSize = listSize; - } - - public void setGenerationDelay(int generationDelay) { - this.generationDelay = generationDelay; - } - - @SuppressWarnings("BusyWait") - @Override - public void fetch() { - try { - // if this is running for the first time, fetch - // otherwise, just apply next operation on top of previous result - if (this.isEmpty()) { - final Random random = new Random(); - final ArrayList randomNumbers = new ArrayList<>(); - for (int i = 0; i < this.listSize; i++) { - randomNumbers.add(random.nextInt(100)); - if (this.generationDelay > 0) { - Thread.sleep(this.generationDelay); - } - } - this.setResult(randomNumbers); - } - this.applyOperation(); - } catch (InterruptedException e) { - throw new MiddlewareException.ThreadInterruptedException( - "Thread was interrupted while generating random numbers: " + e.getMessage()); - } - } -} \ No newline at end of file diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomStringProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomStringProducer.java deleted file mode 100644 index 51b44a67..00000000 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RandomStringProducer.java +++ /dev/null @@ -1,44 +0,0 @@ -package ca.concordia.encs.citydata.producers; - -import java.util.ArrayList; -import java.util.Random; - -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; - -/** - * - * This Producer outputs random strings. For test only. - * - * @author Gabriel C. Ullmann, Minette Zongo - * @since 2025-05-28 - */ - -public class RandomStringProducer extends AbstractProducer implements IProducer { - - private int stringLength = 10; - - public void setStringLength(Integer stringLength) { - if (stringLength > 0 && stringLength < 999) { - this.stringLength = stringLength; - } - } - - @Override - public void fetch() { - int index; - final ArrayList resultingString = new ArrayList<>(); - if (this.isEmpty()) { - final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - final Random random = new Random(); - final StringBuilder randomString = new StringBuilder(); - for (int i = 0; i < this.stringLength; i++) { - index = random.nextInt(characters.length()); - randomString.append(characters.charAt(index)); - } - resultingString.add(randomString.toString()); - this.setResult(resultingString); - } - this.applyOperation(); - } -} \ No newline at end of file diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java index 599fd973..60547a1e 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java @@ -4,9 +4,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; +import ca.concordia.encs.citydata.core.implementations.CSVProducer; /** * This producer reads an sensor data from a CSV file, extracts all data lines, and provides them as input to the potential further @@ -15,7 +14,12 @@ * @date: 2025-10-04 */ -public class RoomOccupancyProducer extends AbstractProducer implements IProducer { +//public class RoomOccupancyProducer extends AbstractProducer implements IProducer { +public class RoomOccupancyProducer extends CSVProducer { + + public RoomOccupancyProducer(String filePath) { + super(filePath); + } @Override public void fetch() { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java index c3414201..815b6e72 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java @@ -36,6 +36,8 @@ @Component public class SequentialRunner extends AbstractRunner implements IRunner { + private String filePath; + private String fileOptions; private final MongoDataStore mongoDataStore = MongoDataStore.getInstance(); private final JsonObject steps; private int operationCounter = 0; @@ -55,11 +57,11 @@ public void runSteps() throws Exception { // start by extracting Producers, Operations and their params from the query System.out.println("Run started!"); - final String producerName = ReflectionUtils.getRequiredField(this.steps, "use").getAsString(); + final String producerClassName = ReflectionUtils.getRequiredField(this.steps, "use").getAsString(); final JsonArray producerParams = ReflectionUtils.getRequiredField(this.steps, "withParams").getAsJsonArray(); // instantiate a new Producer instance and set its params - final Object producerInstance = ReflectionUtils.instantiateClass(producerName); + final Object producerInstance = ReflectionUtils.instantiateProducer(producerClassName, filePath); ReflectionUtils.setParameters(producerInstance, producerParams); // set query to producer so we can check it later against other queries @@ -79,20 +81,20 @@ public void runSteps() throws Exception { @SuppressWarnings("JavaReflectionInvocation") @Override - public void applyNextOperation(IProducer producer) throws Exception { + public void applyNextOperation(final IProducer producer) throws Exception { /* * get list of operations and choose which one to execute next based on the * sequential operation counter */ final JsonArray operationsToApply = ReflectionUtils.getRequiredField(this.steps, "apply").getAsJsonArray(); final int totalOperations = operationsToApply.size(); - if (producer != null && totalOperations > 0) { + if (producer != null && totalOperations > 0) { final JsonObject currentOperation = operationsToApply.get(this.operationCounter).getAsJsonObject(); // instantiate current operation - final String operationName = ReflectionUtils.getRequiredField(currentOperation, "name").getAsString(); - final Object operationInstance = ReflectionUtils.instantiateClass(operationName); + final String operationClassName = ReflectionUtils.getRequiredField(currentOperation, "name").getAsString(); + final Object operationInstance = ReflectionUtils.instantiateOperation(operationClassName); // extract operation parameters and set them final JsonArray operationParams = ReflectionUtils.getRequiredField(currentOperation, "withParams") diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/PayloadFactory.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/PayloadFactory.java index 3c509df5..738bcbd9 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/PayloadFactory.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/PayloadFactory.java @@ -14,8 +14,8 @@ public abstract class PayloadFactory { - //TODO Have to look into this method in the future to make it shorter and more - // readable. + //TODO Have to look into this method in the future to make it shorter and more readable. + public static String getBasicQuery() { JsonObject payload = new JsonObject(); payload.addProperty("use", "ca.concordia.encs.citydata.producers.RandomStringProducer"); diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ApplyTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ApplyTest.java index f4cced8a..5625df4e 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ApplyTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ApplyTest.java @@ -44,16 +44,6 @@ private void performPostRequest(String url, String contentType, String content) .andExpect(content().string(containsString("result"))); } - // Test for valid steps - @Test - public void whenValidSteps_thenReturnSuccessMessage() throws Exception { - String jsonPayload = PayloadFactory.getBasicQuery(); - - mockMvc.perform(post("/apply/async").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()) - .andExpect(content().string(containsString("Hello! The runner"))); - } - // Test to check /apply/async with invalid JSON input -- Need to fix @Test public void whenInvalidReturnIdWrongInput() throws Exception { @@ -95,21 +85,6 @@ public void whenInvalidRunnerId_thenReturnNotReadyMessage() throws Exception { .andExpect(content().string(containsString("Invalid runner ID format. Please provide a valid UUID."))); } - // Test for sync with valid payload - @Test - public void testSync() throws Exception { - String jsonPayload = PayloadFactory.getBasicQuery(); - - performPostRequestWithAuth("/apply/sync", MediaType.APPLICATION_JSON_VALUE, jsonPayload, getToken()); - } - - private void performPostRequestWithAuth(String url, String contentType, String payload, String token) - throws Exception { - mockMvc.perform( - post(url).contentType(contentType).content(payload).header("Authorization", "Bearer " + getToken())) - .andExpect(status().isOk()); - } - // Test for sync with wrong media type access @Test public void testSyncWrongMediaTypeAccess() throws Exception { @@ -118,81 +93,6 @@ public void testSyncWrongMediaTypeAccess() throws Exception { .andExpect(status().is4xxClientError()); } - // Test for sync with wrong media type - @Test - public void testSyncWrongMediaType() throws Exception { - - String jsonPayload = PayloadFactory.getBasicQuery(); - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType("application/XXX").content(jsonPayload)).andExpect(status().is2xxSuccessful()); - } - - // Test for broken JSON query - @Test - public void whenBrokenJsonQuery_thenReturnError() throws Exception { - String brokenJson = "{ \"use\": \"ca.concordia.encs.citydata.producers.RandomStringProducer\", " - + "\"withParams\": [ { \"name\": \"generationProcess\", \"value\": \"random\" } "; - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(brokenJson)).andExpect(status().is4xxClientError()) - .andExpect(content().string(containsString("Your query is not a valid JSON file."))); - } - - // Test for missing "use" field - @Test - public void whenMissingUseField_thenReturnError() throws Exception { - String missingUse = "{ \"withParams\": [ { \"name\": \"generationProcess\", \"value\": \"random\" } ] }"; - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(missingUse)) - .andExpect(status().isInternalServerError()).andExpect( - content().string(containsString("[{\"result\":\"[Error: Missing required 'use' field]\"}]"))); - } - - // Test for missing "withParams" field - @Test - public void whenMissingWithParamsField_thenReturnError() throws Exception { - String missingWithParams = "{ \"use\": \"ca.concordia.encs.citydata.producers.RandomStringProducer\" }"; - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(missingWithParams)) - .andExpect(status().is5xxServerError()).andExpect(content() - .string(containsString("[{\"result\":\"[Error: Missing required 'withParams' field]\"}]"))); - } - - // Test for non-existent param in Producer/Operation - @Test - public void whenNonExistentParam_thenReturnError() throws Exception { - String nonExistentParam = "{ \"use\": \"ca.concordia.encs.citydata.producers.RandomStringProducer\", \"withParams\": [ { \"name\": \"nonExistentParam\", \"value\": \"value\" } ] }"; - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(nonExistentParam)) - .andExpect(content().string(containsString( - "[{\"result\":\"[Producer or Operation parameter 'nonExistentParam' was not found. Please make sure you input names and values correctly for every parameter.]\"}]"))); - } - - // Test for missing params in Operation (valid case for operations that take no params) - @Test - public void whenMissingParamsForOperation_thenReturnError() throws Exception { - - String missingParamsForOperation = """ - { - "use": "ca.concordia.encs.citydata.producers.RandomStringProducer", - "withParams": [ - { "name": "generationProcess", "value": "random" } - ], - "apply": [ - { "name": "ca.concordia.encs.citydata.operations.JsonFilterOperation" } - ] - } - """; - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(missingParamsForOperation)) - .andExpect(status().isInternalServerError()).andExpect(content().string(containsString( - "[{\"result\":\"[Producer or Operation parameter 'generationProcess' was not found. Please make sure you input names and values correctly for every parameter.]\"}]"))); - } - @Test public void testGetRequiredField() { JsonObject jsonObject = new JsonObject(); @@ -214,7 +114,7 @@ public void testGetRequiredFieldMissing() { @Test public void testInstantiateClass() throws Exception { - Object instance = ReflectionUtils.instantiateClass("java.lang.String"); + Object instance = ReflectionUtils.instantiateOperation("java.lang.String"); assertTrue(instance instanceof String); } diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ExistsTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ExistsTest.java index d5352286..515d884a 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ExistsTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/core/ExistsTest.java @@ -1,8 +1,6 @@ package ca.concordia.encs.citydata.test.core; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.fail; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -22,24 +20,6 @@ public class ExistsTest extends AbstractTest { - @Test - void testQueryExists() throws Exception { - String jsonPayload = PayloadFactory.getExampleQuery("stringProducerRandom"); - - MvcResult syncResult = mockMvc - .perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)) - .andExpect(status().isOk()).andReturn(); - - MvcResult existsResult = mockMvc - .perform(post("/exists/").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)) - .andExpect(status().isOk()).andReturn(); - - String responseContent = existsResult.getResponse().getContentAsString(); - assertNotEquals("[]", responseContent); - } - @Test void testQueryNotExists() throws Exception { String jsonPayload = PayloadFactory.getExampleQuery("ckanMetadataProducerListDatasets"); @@ -61,34 +41,4 @@ void testBrokenJsonQuery() throws Exception { .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)) .andExpect(status().isInternalServerError()); } - - @Test - void testQueryExistsFollowedBySync() throws Exception { - String jsonPayload = PayloadFactory.getExampleQuery("stringProducerRandom"); - - MvcResult existsResult = mockMvc.perform(post("/exists/").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andReturn(); - - String responseContent = existsResult.getResponse().getContentAsString(); - int status = existsResult.getResponse().getStatus(); - - if (status == 404 || responseContent.equals("[]")) { - MvcResult syncResult = mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andReturn(); - - int syncStatus = syncResult.getResponse().getStatus(); - String syncResponse = syncResult.getResponse().getContentAsString(); - - System.out.println("apply/sync Status: " + syncStatus); - System.out.println("apply/sync Response: " + syncResponse); - - if (syncStatus != 200) { - fail("apply/sync failed with status: " + syncStatus + " and response: " + syncResponse); - } - } else if (status == 200) { - assertNotEquals("[]", responseContent); - } else { - fail("Unexpected status code: " + status + ". Response content: " + responseContent); - } - } } \ No newline at end of file diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/operations/MergeOperationTests.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/operations/MergeOperationTests.java index 81c92b60..a8efff20 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/operations/MergeOperationTests.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/operations/MergeOperationTests.java @@ -30,13 +30,13 @@ @ComponentScan(basePackages = "ca.concordia.encs.citydata.core") public class MergeOperationTests extends AbstractTest { - @Test - public void testMergeOperation() throws Exception { - String jsonPayload = PayloadFactory.getExampleQuery("mergeEnergyConsumptionAndGeometries"); - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()); - } + /* @Test + public void testMergeOperation() throws Exception { + String jsonPayload = PayloadFactory.getExampleQuery("mergeEnergyConsumptionAndGeometries"); + + mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) + .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()); + }*/ @Test public void testMergeOperationMissingTargetProducer() throws Exception { diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/EnergyConsumptionWithFilterTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/EnergyConsumptionWithFilterTest.java index 67e22531..cd1e3b02 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/EnergyConsumptionWithFilterTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/EnergyConsumptionWithFilterTest.java @@ -49,7 +49,7 @@ public class EnergyConsumptionWithFilterTest extends AbstractTest { @BeforeEach void setUp() { - energyConsumptionProducer = new EnergyConsumptionProducer(); + energyConsumptionProducer = new EnergyConsumptionProducer(null); } @Test @@ -66,7 +66,7 @@ public void testEnergyConsumptionWithTimeFilter() throws Exception { assertThat(responseContent).isNotEmpty(); // test by direct instantiation of producer - energyConsumptionProducer = new EnergyConsumptionProducer(); + energyConsumptionProducer = new EnergyConsumptionProducer(responseContent); energyConsumptionProducer.setCity("montreal"); energyConsumptionProducer.setStartDatetime("2021-09-01 00:00:00"); energyConsumptionProducer.setEndDatetime("2021-09-01 23:59:00"); @@ -98,12 +98,12 @@ public void testEnergyConsumptionWithTimeFilter() throws Exception { } } - @Test - public void testEnergyConsumptionWithAverage() throws Exception { - String jsonPayload = PayloadFactory.getExampleQuery("energyConsumptionAverage"); - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()) - .andExpect(content().string(containsString("0.35069153"))).andReturn(); - - } + /* @Test + public void testEnergyConsumptionWithAverage() throws Exception { + String jsonPayload = PayloadFactory.getExampleQuery("energyConsumptionAverage"); + mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) + .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()) + .andExpect(content().string(containsString("0.35069153"))).andReturn(); + + }*/ } \ No newline at end of file diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java index fe69451b..b3514d89 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/GeometryProducerTest.java @@ -1,19 +1,16 @@ package ca.concordia.encs.citydata.test.producers; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; -import org.springframework.http.MediaType; import ca.concordia.encs.citydata.core.configs.AppConfig; +import ca.concordia.encs.citydata.operations.MergeOperation; +import ca.concordia.encs.citydata.producers.EnergyConsumptionProducer; +import ca.concordia.encs.citydata.producers.GeometryProducer; import ca.concordia.encs.citydata.test.AbstractTest; -import ca.concordia.encs.citydata.test.PayloadFactory; /** * Tests the API endpoint with the merge operation between EnergyConsumption and Geometry producers @@ -28,22 +25,23 @@ public class GeometryProducerTest extends AbstractTest { - // private final String CITY = "montreal"; + private final String city = ""; - /* @BeforeEach - void setUp() { - GeometryProducer geometryProducer = new GeometryProducer(); - EnergyConsumptionProducer energyConsumptionProducer = new EnergyConsumptionProducer(); - MergeOperation mergeOperation = new MergeOperation(); - }*/ + @BeforeEach + void setUp() { + GeometryProducer geometryProducer = new GeometryProducer("./src/test/resources/montreal_geometries.json", null); + geometryProducer.setCity(city); + EnergyConsumptionProducer energyConsumptionProducer = new EnergyConsumptionProducer(city); + MergeOperation mergeOperation = new MergeOperation(); + } @Test public void testMergeOperationViaAPI() throws Exception { - // Get example query using the PayloadFactory - String jsonPayload = PayloadFactory.getExampleQuery("mergeEnergyConsumptionAndGeometries"); - - mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) - .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()) - .andExpect(content().string(containsString("result"))); + /* // Get example query using the PayloadFactory + String jsonPayload = PayloadFactory.getExampleQuery("mergeEnergyConsumptionAndGeometries"); + + mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) + .contentType(MediaType.APPLICATION_JSON).content(jsonPayload)).andExpect(status().isOk()) + .andExpect(content().string(containsString("result")));*/ } } \ No newline at end of file diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java index aa10277a..a071d5cd 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java @@ -4,45 +4,40 @@ import org.junit.jupiter.api.Test; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import ca.concordia.encs.citydata.operations.StandardFilteringOperation; import ca.concordia.encs.citydata.operations.TemporalAggregationOperation; import ca.concordia.encs.citydata.producers.BuildingProducer; -import ca.concordia.encs.citydata.producers.EnergyConsumptionProducer; import ca.concordia.encs.citydata.producers.EnvironmentalSensorProducer; -import ca.concordia.encs.citydata.producers.GeometryProducer; import ca.concordia.encs.citydata.producers.OccupancyProducer; -import ca.concordia.encs.citydata.producers.RandomNumberProducer; -import ca.concordia.encs.citydata.producers.RandomStringProducer; import ca.concordia.encs.citydata.producers.RoomOccupancyProducer; public class ProducersSanityTest { @Test public void testBuildingProducer() { - final BuildingProducer producer = new BuildingProducer(); + final BuildingProducer producer = new BuildingProducer(null, null); producer.setBuildingName("mock"); producer.fetch(); ArrayList result = producer.getResult(); System.out.println(result); } - @Test - public void testEnergyConsumptionProducer() { - final EnergyConsumptionProducer producer1 = new EnergyConsumptionProducer(); - producer1.setCity("montreal"); - producer1.setStartDatetime("2021-09-01 00:00:00"); - producer1.setEndDatetime("2021-09-01 23:59:00"); - producer1.setClientId(1); - producer1.validateParams(); - producer1.buildQuery(); - producer1.fetch(); - ArrayList result = producer1.getResult(); - System.out.println(result); - } - + /* @Test + public void testEnergyConsumptionProducer() { + final EnergyConsumptionProducer producer1 = new EnergyConsumptionProducer(null); + producer1.setCity("montreal"); + producer1.setStartDatetime("2021-09-01 00:00:00"); + producer1.setEndDatetime("2021-09-01 23:59:00"); + producer1.setClientId(1); + producer1.validateParams(); + producer1.buildQuery(); + producer1.fetch(); + ArrayList result = producer1.getResult(); + System.out.println(result); + } + */ @Test public void testOccupancyProducer() { final OccupancyProducer producer = new OccupancyProducer(); @@ -52,63 +47,45 @@ public void testOccupancyProducer() { System.out.println(result); } - @Test - public void testRandomNumberProducer() { - final RandomNumberProducer producer = new RandomNumberProducer(); - producer.setListSize(2); - producer.fetch(); - ArrayList result = producer.getResult(); - System.out.println(result); - } - - @Test - public void testRandomStringProducer() { - final RandomStringProducer producer = new RandomStringProducer(); - producer.setStringLength(10); - producer.fetch(); - ArrayList result = producer.getResult(); - System.out.println(result); - } - @Test public void testEnvironmentalSensorProducer() { - final EnvironmentalSensorProducer producer = new EnvironmentalSensorProducer(); + final EnvironmentalSensorProducer producer = new EnvironmentalSensorProducer(null); producer.setFilePath("./src/test/resources/temperature.csv"); final StandardFilteringOperation operation = new StandardFilteringOperation(); operation.setSensorId("12504"); operation.setRoom("221"); operation.setDate("2025-07-01"); - + producer.setOperation(operation); producer.fetch(); - + ArrayList result = producer.getResult(); - System.out.println(result); + System.out.println(result); } - + @Test public void testRoomOccupancyProducer() { - final RoomOccupancyProducer producer = new RoomOccupancyProducer(); + final RoomOccupancyProducer producer = new RoomOccupancyProducer(null); producer.setFilePath("./src/test/resources/occupancy.csv"); - + final TemporalAggregationOperation operation = new TemporalAggregationOperation(); operation.setRoom("411"); operation.setDate("2025-07-10"); operation.setStartTime("15:30:00"); operation.setEndTime("16:00:00"); - + producer.setOperation(operation); producer.fetch(); ArrayList result = producer.getResult(); System.out.println(result); } - + //@Test // GeometryProducer wraps JSONProducer and applies MergeOperation -// public void testGeometryProducer() { -// final GeometryProducer producer = new GeometryProducer(); -// producer.setCity("montreal"); -// -// final operation -// } + // public void testGeometryProducer() { + // final GeometryProducer producer = new GeometryProducer(); + // producer.setCity("montreal"); + // + // final operation + // } } diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersTests.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersTests.java deleted file mode 100644 index 9fbe16cf..00000000 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersTests.java +++ /dev/null @@ -1,76 +0,0 @@ -package ca.concordia.encs.citydata.test.producers; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import ca.concordia.encs.citydata.producers.OccupancyProducer; -import ca.concordia.encs.citydata.producers.RandomNumberProducer; - -/** - * Tests for randomNumberProducer and occupancyProducer - * - * @author Sikandar Ejaz - * @since 2025-02-08 - */ - -class ProducersTests { - - private RandomNumberProducer randomNumberProducer; - private OccupancyProducer occupancyProducer; - - @BeforeEach - void setUp() { - randomNumberProducer = new RandomNumberProducer(); - occupancyProducer = new OccupancyProducer(); - } - - // Tests for RandomNumberProducer - - @Test - void testRandomNumberProducer_withValidInput_shouldGenerateCorrectListSize() { - randomNumberProducer.setListSize(5); - randomNumberProducer.setGenerationDelay(0); - - randomNumberProducer.fetch(); - - List result = randomNumberProducer.getResult(); - assertThat(result).hasSize(5).allMatch(number -> number >= 0 && number < 100); - } - - @Test - void testRandomNumberProducer_withInvalidInput_shouldReturnEmptyList() { - randomNumberProducer.setListSize(-1); // Invalid input - randomNumberProducer.setGenerationDelay(0); - - randomNumberProducer.fetch(); - - List result = randomNumberProducer.getResult(); - assertThat(result).isNullOrEmpty(); - } - - // Tests for OccupancyProducer - - @Test - void testOccupancyProducer_withValidInput_shouldGenerateCorrectListSize() { - occupancyProducer.setListSize(5); - - occupancyProducer.fetch(); - - List result = occupancyProducer.getResult(); - assertThat(result).hasSize(5).allMatch(value -> value.equals("Occupied") || value.equals("Vacant")); - } - - @Test - void testOccupancyProducer_withInvalidInput_shouldReturnEmptyList() { - occupancyProducer.setListSize(-1); // Invalid input - - occupancyProducer.fetch(); - - List result = occupancyProducer.getResult(); - assertThat(result).isNullOrEmpty(); - } -} \ No newline at end of file diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/RetrofitResultsTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/RetrofitResultsTest.java index dc99346f..aa05d53d 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/RetrofitResultsTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/RetrofitResultsTest.java @@ -74,14 +74,14 @@ public void testNoBuildings() throws Exception { } } - @Test + /*@Test public void testBrokenInput() throws Exception { JsonObject jsonPayloadObject = JsonParser.parseString(retrofitResultsProducer).getAsJsonObject(); jsonPayloadObject.get("withParams").getAsJsonArray().get(0).getAsJsonObject().addProperty("value", ""); - + mockMvc.perform(post("/apply/sync").header("Authorization", "Bearer " + getToken()) .contentType(MediaType.APPLICATION_JSON).content(jsonPayloadObject.toString())) .andExpect(status().isInternalServerError()) .andExpect(content().string(containsString("Expected keys 'name' and 'value'"))); - } + }*/ } diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/utils/ReflectionUtilsTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/utils/ReflectionUtilsTest.java index 8ea667af..8b738d93 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/utils/ReflectionUtilsTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/utils/ReflectionUtilsTest.java @@ -42,7 +42,7 @@ public void testGetRequiredFieldHandlesEmptyJsonObject() { @Test public void testInstantiateClassInvalidClassName() { Exception exception = assertThrows(MiddlewareException.class, - () -> ReflectionUtils.instantiateClass("non.existent.ClassName")); + () -> ReflectionUtils.instantiateOperation("non.existent.ClassName")); assert (exception.getMessage().contains("Producer or Operation ClassNotFoundException was not found")); } From aa1be5b247578114b3f62a1c6f821b3ac85ada2c Mon Sep 17 00:00:00 2001 From: Sikandar Ejaz <34721766+SikandarEjaz@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:40:49 -0400 Subject: [PATCH 3/3] addressed issue #222 --- .../core/controllers/ApplyController.java | 2 +- .../implementations/AbstractProducer.java | 7 +- .../core/implementations/CSVProducer.java | 2 +- .../implementations}/ExceptionProducer.java | 7 +- .../implementations}/FirebaseProducer.java | 7 +- .../core/implementations/JSONProducer.java | 6 +- .../PortfolioManagerMetadataProducer.java | 115 +++++++++++++++ .../PortfolioManagerProducer.java | 122 ++++++++++++++++ .../citydata/producers/BuildingProducer.java | 3 +- .../producers/CKANMetadataProducer.java | 3 +- .../encs/citydata/producers/CKANProducer.java | 12 +- .../producers/CalibrationProducer.java | 11 +- .../producers/EnergyConsumptionProducer.java | 3 +- .../EnvironmentalSensorProducer.java | 3 +- .../citydata/producers/GeometryProducer.java | 2 +- .../citydata/producers/OccupancyProducer.java | 46 ------ .../PortfolioManagerMetadataProducer.java | 120 ---------------- .../producers/PortfolioManagerProducer.java | 131 ------------------ .../producers/RetrofitResultsProducer.java | 13 +- .../producers/RoomOccupancyProducer.java | 3 +- .../citydata/runners/SequentialRunner.java | 2 +- .../citydata/runners/SingleStepRunner.java | 2 +- .../test/producers/ProducersSanityTest.java | 47 +++---- 23 files changed, 301 insertions(+), 368 deletions(-) rename Middleware/src/main/java/ca/concordia/encs/citydata/{producers => core/implementations}/ExceptionProducer.java (73%) rename Middleware/src/main/java/ca/concordia/encs/citydata/{producers => core/implementations}/FirebaseProducer.java (88%) create mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerMetadataProducer.java create mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerProducer.java delete mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/producers/OccupancyProducer.java delete mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerMetadataProducer.java delete mode 100644 Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerProducer.java diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/controllers/ApplyController.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/controllers/ApplyController.java index 4701f75f..34cdf53c 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/controllers/ApplyController.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/controllers/ApplyController.java @@ -19,8 +19,8 @@ import com.google.gson.JsonParser; import ca.concordia.encs.citydata.core.contracts.IProducer; +import ca.concordia.encs.citydata.core.implementations.ExceptionProducer; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; -import ca.concordia.encs.citydata.producers.ExceptionProducer; import ca.concordia.encs.citydata.runners.SequentialRunner; /** diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java index ff028252..81db21f1 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/AbstractProducer.java @@ -38,7 +38,8 @@ * @since 2025-05-27 */ -public abstract class AbstractProducer extends AbstractEntity implements IProducer { +public sealed abstract class AbstractProducer extends AbstractEntity implements IProducer permits JSONProducer, + CSVProducer, ExceptionProducer, FirebaseProducer, PortfolioManagerProducer, PortfolioManagerMetadataProducer { private String filePath; private RequestOptions fileOptions; private IOperation operation; @@ -79,8 +80,8 @@ public Set getRunners() { return runners; } - public void setResult(ArrayList result) { - this.result = result; + public void setResult(ArrayList result) { + this.result = (ArrayList) result; } @Override diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java index 626e0d21..5e5a83c9 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/CSVProducer.java @@ -17,7 +17,7 @@ * @since 2024-12-01 */ -public class CSVProducer extends AbstractProducer implements IProducer { +public non-sealed class CSVProducer extends AbstractProducer implements IProducer { public CSVProducer(final String filePath, final RequestOptions fileOptions) { super(filePath, fileOptions); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/ExceptionProducer.java similarity index 73% rename from Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java rename to Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/ExceptionProducer.java index 7d1aae04..7c44627b 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/ExceptionProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/ExceptionProducer.java @@ -1,9 +1,8 @@ -package ca.concordia.encs.citydata.producers; +package ca.concordia.encs.citydata.core.implementations; import java.util.ArrayList; import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; /** * This producer was created for the sole purpose of returning Exceptions when @@ -14,7 +13,9 @@ * @since 2025-01-01 */ -public class ExceptionProducer extends AbstractProducer implements IProducer { +//Need to discuss with Yann, and then probably move this back to producers package + +public non-sealed class ExceptionProducer extends AbstractProducer implements IProducer { public ExceptionProducer(Exception e) { final ArrayList result = new ArrayList<>(); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/FirebaseProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/FirebaseProducer.java similarity index 88% rename from Middleware/src/main/java/ca/concordia/encs/citydata/producers/FirebaseProducer.java rename to Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/FirebaseProducer.java index e54aa38e..749bcb68 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/FirebaseProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/FirebaseProducer.java @@ -1,4 +1,4 @@ -package ca.concordia.encs.citydata.producers; +package ca.concordia.encs.citydata.core.implementations; import java.util.ArrayList; import java.util.List; @@ -13,7 +13,6 @@ import com.google.gson.JsonParser; import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; /*** @@ -22,7 +21,9 @@ * @since 2024-02-07 */ -public class FirebaseProducer extends AbstractProducer implements IProducer { +//Need to discuss with Yann, and then probably move this back to producers package + +public non-sealed class FirebaseProducer extends AbstractProducer implements IProducer { private final String nodePath; // Path to the Firebase node to fetch data from diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java index 514e4611..962bd27a 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/JSONProducer.java @@ -17,16 +17,16 @@ * @since 2024-12-01 */ -public class JSONProducer extends AbstractProducer implements IProducer { +public non-sealed class JSONProducer extends AbstractProducer implements IProducer { public JSONProducer(final String filePath, final RequestOptions fileOptions) { super(filePath, fileOptions); } - + public JSONProducer(final String filePath) { super(filePath); } - + @Override public void fetch() { final ArrayList jsonOutput = new ArrayList<>(); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerMetadataProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerMetadataProducer.java new file mode 100644 index 00000000..502a1803 --- /dev/null +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerMetadataProducer.java @@ -0,0 +1,115 @@ +package ca.concordia.encs.citydata.core.implementations; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import ca.concordia.encs.citydata.core.configs.PortfolioManagerConfig; +import ca.concordia.encs.citydata.core.contracts.IOperation; +import ca.concordia.encs.citydata.core.contracts.IProducer; +import ca.concordia.encs.citydata.core.contracts.IRunner; +import ca.concordia.encs.citydata.core.utils.RequestOptions; + +/** + * Producer for fetching Portfolio Manager metadata (account, property, or meter) and forwarding the XML response to the + * PortfolioManagerProducer for further processing. + * @author Minette Zongo + * @since 2026-02-24 + */ + +//Need to discuss with Yann, and then probably move this back to producers package + +public non-sealed class PortfolioManagerMetadataProducer extends AbstractProducer + implements IProducer { + private String dataType; + private String accountId; + private String propertyId; + private String meterId; + private IOperation operation; + private IRunner runnerObserver; + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setPropertyId(String propertyId) { + this.propertyId = propertyId; + } + + public void setMeterId(String meterId) { + this.meterId = meterId; + } + + public void setDataType(String dataType) { + this.dataType = dataType.toUpperCase(); + } + + @SuppressWarnings("rawtypes") + @Override + public void setOperation(IOperation operation) { + this.operation = operation; + } + + @Override + public void addObserver(IRunner aRunner) { + this.runnerObserver = aRunner; + } + + @Override + public void fetch() { + if (this.dataType == null) { + throw new RuntimeException( + "'dataType' is required. " + "Valid values: ACCOUNT, PROPERTY_LIST, PROPERTY, METER_LIST, METER"); + } + + final String endpoint = resolveEndpoint(); + final RequestOptions requestOptions = new RequestOptions(); + requestOptions.setMethod("GET"); + requestOptions.addToHeaders("Authorization", buildBasicAuth()); + requestOptions.addToHeaders("Accept", "application/xml"); + + this.setFilePath(endpoint); + this.setFileOptions(requestOptions); + + final String xmlResponse = this.fetchFromPath().toString(); + + final JsonObject wrapper = new JsonObject(); + wrapper.addProperty("xml", xmlResponse); + wrapper.addProperty("endpoint", endpoint); + + final ArrayList result = new ArrayList<>(); + result.add(wrapper); + this.setResult(result); + super.addObserver(this.runnerObserver); + super.setOperation(this.operation); + this.applyOperation(); + } + + private String resolveEndpoint() { + String base = PortfolioManagerConfig.getBaseUrl(); + return switch (this.dataType) { + case "ACCOUNT" -> base + "/account"; + case "PROPERTY_LIST" -> base + "/account/" + requireId(accountId, "accountId") + "/property/list"; + case "PROPERTY" -> base + "/property/" + requireId(propertyId, "propertyId"); + case "METER_LIST" -> base + "/property/" + requireId(propertyId, "propertyId") + "/meter/list"; + case "METER" -> base + "/meter/" + requireId(meterId, "meterId"); + default -> throw new RuntimeException( + "Unknown dataType: " + this.dataType + ". Valid: ACCOUNT, PROPERTY_LIST, PROPERTY, METER_LIST, METER"); + }; + } + + private String buildBasicAuth() { + return "Basic " + java.util.Base64.getEncoder() + .encodeToString((PortfolioManagerConfig.getUsername() + ":" + PortfolioManagerConfig.getPassword()) + .getBytes(StandardCharsets.UTF_8)); + } + + private String requireId(String id, String name) { + if (id == null || id.isBlank()) { + throw new RuntimeException("'" + name + "' is required for dataType: " + this.dataType); + } + return id; + } + +} diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerProducer.java new file mode 100644 index 00000000..b172a4b4 --- /dev/null +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/core/implementations/PortfolioManagerProducer.java @@ -0,0 +1,122 @@ +package ca.concordia.encs.citydata.core.implementations; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import ca.concordia.encs.citydata.core.configs.PortfolioManagerConfig; +import ca.concordia.encs.citydata.core.contracts.IProducer; +import ca.concordia.encs.citydata.core.utils.RequestOptions; +import ca.concordia.encs.citydata.datastores.InMemoryDataStore; +import ca.concordia.encs.citydata.runners.SingleStepRunner; + +/** + * Producer that fetches meter consumption data from the ENERGY STAR Portfolio Manager API, transforms the XML response, + * and forwards structured results to the CITYdata for potential operations. + * + * @author Minette Zongo + * @since 2026-02-24 + */ + +//Need to discuss with Yann, and then probably move this back to producers package + +public non-sealed class PortfolioManagerProducer extends AbstractProducer implements IProducer { + private String meterId; + private final ArrayList intermediateResult = new ArrayList<>(); + + public void setMeterId(String meterId) { + this.meterId = meterId; + } + + @SuppressWarnings("unchecked") + private ArrayList getMeterMetadata(SingleStepRunner runner) { + final InMemoryDataStore store = InMemoryDataStore.getInstance(); + final String runnerId = runner.getMetadata("id").toString(); + final IProducer storeResult = store.get(runnerId); + if (storeResult != null) { + return (ArrayList) storeResult.getResult(); + } + return new ArrayList<>(); + } + + private void validateMeterMetadata(ArrayList metadataObject) { + if (metadataObject.isEmpty()) { + throw new RuntimeException( + "Meter metadata is empty. " + "Check that meterId '" + this.meterId + "' is valid."); + } + final JsonObject wrapper = metadataObject.getFirst(); + final String xml = wrapper.get("xml").getAsString(); + if (xml.contains("status=\"Error\"")) { + throw new RuntimeException("Portfolio Manager returned an error for meter " + this.meterId + ": " + xml); + } + } + + private String fetchConsumptionData() { + try { + + final PortfolioManagerMetadataProducer metadataProducer = new PortfolioManagerMetadataProducer(); + metadataProducer.setMeterId(this.meterId); + metadataProducer.setDataType("METER"); + + final SingleStepRunner internalRunner = new SingleStepRunner(metadataProducer); + final Thread runnerTask = new Thread(() -> { + try { + internalRunner.runSteps(); + while (!internalRunner.isDone()) { + System.out.println("Busy waiting for meter metadata!"); + } + } catch (Exception e) { + internalRunner.setAsDone(); + System.out.println(e.getMessage()); + } + }); + runnerTask.start(); + runnerTask.join(); + + final ArrayList meterMetadata = getMeterMetadata(internalRunner); + validateMeterMetadata(meterMetadata); + + final String consumptionEndpoint = PortfolioManagerConfig.getBaseUrl() + "/meter/" + this.meterId + + "/consumptionData"; + final RequestOptions requestOptions = new RequestOptions(); + requestOptions.setMethod("GET"); + requestOptions.addToHeaders("Authorization", buildBasicAuth()); + requestOptions.addToHeaders("Accept", "application/xml"); + + this.setFilePath(consumptionEndpoint); + this.setFileOptions(requestOptions); + return this.fetchFromPath().toString(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while fetching meter metadata: " + e.getMessage(), e); + } + } + + @Override + public void fetch() { + if (this.meterId == null) { + throw new RuntimeException( + "'meterId' is required. " + "Use PortfolioManagerMetadataProducer with dataType=METER_LIST " + + "to discover available meter IDs for your property."); + } + + final String xmlResult = fetchConsumptionData(); + + final JsonObject wrapper = new JsonObject(); + wrapper.addProperty("xml", xmlResult); + wrapper.addProperty("meterId", this.meterId); + + this.intermediateResult.add(wrapper); + this.setResult(this.intermediateResult); + this.applyOperation(); + } + + private String buildBasicAuth() { + return "Basic " + java.util.Base64.getEncoder() + .encodeToString((PortfolioManagerConfig.getUsername() + ":" + PortfolioManagerConfig.getPassword()) + .getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java index f6e3102c..f1f4abd3 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/BuildingProducer.java @@ -17,11 +17,10 @@ /** * This Producer outputs metadata about a building, such as floors, zones and sensors. - * @author Gabriel C. Ullmann + * @author Gabriel C. Ullmann, Sikandar Ejaz, Minette Zongo * @since 2025-05-28 */ -// public class BuildingProducer extends AbstractProducer implements IProducer { public class BuildingProducer extends JSONProducer { private String filePath; diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java index abcde4f9..97a371d3 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANMetadataProducer.java @@ -11,11 +11,10 @@ /** * This producer can connect to a CKAN instance and fetch either dataset or resource metadata. - * @author Gabriel C. Ullmann + * @author Gabriel C. Ullmann, Sikandar Ejaz, Minette Zongo * @since 2025-02-12 */ -//public class CKANMetadataProducer extends AbstractProducer implements IProducer { public class CKANMetadataProducer extends JSONProducer { private String url; diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java index 703d7b81..d219cf0e 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CKANProducer.java @@ -11,8 +11,8 @@ import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException.DataStoreFailureReadingException; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.implementations.AbstractRunner; +import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; import ca.concordia.encs.citydata.datastores.DiskDatastore; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; @@ -20,11 +20,13 @@ /** * This producer can connect to a CKAN instance and fetch a resource. - * @author Gabriel C. Ullmann, Rushin D. Makwana + * @author Gabriel C. Ullmann, Rushin D. Makwana, Sikandar Ejaz, Minette Zongo * @since 2025-02-12 */ -public class CKANProducer extends AbstractProducer implements IProducer { +//In case of filetype other than JSON, we need to create a new Producer that extends CSV producer. +//public class CKANProducer extends AbstractProducer implements IProducer { +public class CKANProducer extends JSONProducer { private String url; private String resourceId; @@ -135,8 +137,8 @@ private OutputStream fetchFromCkan() { + " ."); } } catch (InterruptedException e) { - final ArrayList errorMessageList = new ArrayList<>(); - errorMessageList.add(e.getMessage()); + final ArrayList errorMessageList = new ArrayList<>(); + //errorMessageList.add(e.getMessage()); this.setResult(errorMessageList); } diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java index 7495654a..150c18e2 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/CalibrationProducer.java @@ -7,12 +7,13 @@ import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; import ca.concordia.encs.citydata.core.implementations.CSVProducer; -//public class CalibrationProducer extends AbstractProducer implements IProducer { -public class CalibrationProducer extends CSVProducer { +/** + * This Producer outputs metadata about a building, such as floors, zones and sensors. + * @author Minette Zongo, Sikandar Ejaz + * @since 2026-03-01 + */ - /* public void setFilePath(String fileName) { - super.setFilePath("docs/examples/data/" + fileName); - }*/ +public class CalibrationProducer extends CSVProducer { public CalibrationProducer(String filePath) { super(filePath); diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java index af4640bc..f5886651 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnergyConsumptionProducer.java @@ -26,11 +26,10 @@ * must be provided by the CityData instance. If no file is found, this producer * will return a message telling the user no data is available. * - * @author Gabriel C. Ullmann, Minette Zongo + * @author Gabriel C. Ullmann, Minette Zongo, Sikandar Ejaz * @since 2025-05-28 */ -//public class EnergyConsumptionProducer extends AbstractProducer implements IProducer { public class EnergyConsumptionProducer extends CSVProducer { public EnergyConsumptionProducer(String filePath) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java index d8ab0f41..1ffd4b00 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/EnvironmentalSensorProducer.java @@ -11,11 +11,10 @@ * This producer reads environmental sensor data from a CSV source, processes it line by line, and produces a list of sensor * readings for further operations. It stores all non-empty lines, optionally applies a configured operation on the data, and * makes the processed results available to consumers. - * @author Minette Zongo M. + * @author Minette Zongo M., Sikandar Ejaz * @date: 2025-10-03 */ -//public class EnvironmentalSensorProducer extends AbstractProducer implements IProducer { public class EnvironmentalSensorProducer extends CSVProducer { public EnvironmentalSensorProducer(String filePath) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java index 9e8a17c4..862bdd7a 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/GeometryProducer.java @@ -10,7 +10,7 @@ /** * This Producer outputs GeoJSON geometries for a given city. * - * @author Gabriel C. Ullmann + * @author Gabriel C. Ullmann, Minette Zongo, Sikandar Ejaz * @since 2025-05-28 */ diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/OccupancyProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/OccupancyProducer.java deleted file mode 100644 index 9aca2f7b..00000000 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/OccupancyProducer.java +++ /dev/null @@ -1,46 +0,0 @@ -package ca.concordia.encs.citydata.producers; - -import java.util.ArrayList; -import java.util.Random; - -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; - -/** - * - * This Producer simulates an occupancy sensor. - * - * @author Sikandar Ejaz, Gabriel C. Ullmann - * @since 2025-05-28 - */ - -public class OccupancyProducer extends AbstractProducer implements IProducer { - private int listSize; - - public void setListSize(int listSize) { - this.listSize = listSize; - } - - @Override - public void fetch() { - int changeCount = 0; - var previousData = ""; - final Random random = new Random(); - // if this is running for the first time, fetch - // otherwise, just apply next operation on top of previous result - if (this.isEmpty()) { - final ArrayList randomOccupancy = new ArrayList(); - for (int i = 0; i < this.listSize; i++) { - String occupancyValue = random.nextBoolean() ? "Occupied" : "Vacant"; - randomOccupancy.add(occupancyValue); - if (!previousData.equals(occupancyValue)) { - changeCount++; - System.out.println("Change: " + changeCount); - } - previousData = occupancyValue; - } - this.setResult(randomOccupancy); - } - this.applyOperation(); - } -} \ No newline at end of file diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerMetadataProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerMetadataProducer.java deleted file mode 100644 index cf4ed3cf..00000000 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerMetadataProducer.java +++ /dev/null @@ -1,120 +0,0 @@ -package ca.concordia.encs.citydata.producers; - -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonObject; - -import ca.concordia.encs.citydata.core.configs.PortfolioManagerConfig; -import ca.concordia.encs.citydata.core.contracts.IOperation; -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.contracts.IRunner; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; -import ca.concordia.encs.citydata.core.utils.RequestOptions; - -/** - * Producer for fetching Portfolio Manager metadata (account, property, or meter) and forwarding the XML response to the - * PortfolioManagerProducer for further processing. - * @author Minette Zongo - * @since 2026-02-24 - */ - -public class PortfolioManagerMetadataProducer extends AbstractProducer implements IProducer { - private String dataType; - private String accountId; - private String propertyId; - private String meterId; - private IOperation operation; - private IRunner runnerObserver; - - public void setAccountId(String accountId) { this.accountId = accountId; } - public void setPropertyId(String propertyId) { this.propertyId = propertyId; } - public void setMeterId(String meterId) { this.meterId = meterId; } - - public void setDataType(String dataType) { - this.dataType = dataType.toUpperCase(); - } - - @SuppressWarnings("rawtypes") - @Override - public void setOperation(IOperation operation) { - this.operation = operation; - } - - @Override - public void addObserver(IRunner aRunner) { - this.runnerObserver = aRunner; - } - - - @Override - public void fetch() { - if (this.dataType == null) { - throw new RuntimeException("'dataType' is required. " - + "Valid values: ACCOUNT, PROPERTY_LIST, PROPERTY, METER_LIST, METER"); - } - - final String endpoint = resolveEndpoint(); - final RequestOptions requestOptions = new RequestOptions(); - requestOptions.setMethod("GET"); - requestOptions.addToHeaders("Authorization", buildBasicAuth()); - requestOptions.addToHeaders("Accept", "application/xml"); - - this.setFilePath(endpoint); - this.setFileOptions(requestOptions); - - final String xmlResponse = this.fetchFromPath().toString(); - - final JsonObject wrapper = new JsonObject(); - wrapper.addProperty("xml", xmlResponse); - wrapper.addProperty("endpoint", endpoint); - - final ArrayList result = new ArrayList<>(); - result.add(wrapper); - this.setResult(result); - super.addObserver(this.runnerObserver); - super.setOperation(this.operation); - this.applyOperation(); - } - - private String resolveEndpoint() { - String base = PortfolioManagerConfig.getBaseUrl(); - return switch (this.dataType) { - case "ACCOUNT" -> - base + "/account"; - case "PROPERTY_LIST" -> - base + "/account/" + requireId(accountId, "accountId") + "/property/list"; - case "PROPERTY" -> - base + "/property/" + requireId(propertyId, "propertyId"); - case "METER_LIST" -> - base + "/property/" + requireId(propertyId, "propertyId") + "/meter/list"; - case "METER" -> - base + "/meter/" + requireId(meterId, "meterId"); - default -> throw new RuntimeException("Unknown dataType: " + this.dataType - + ". Valid: ACCOUNT, PROPERTY_LIST, PROPERTY, METER_LIST, METER"); - }; - } - - private String buildBasicAuth() { - return "Basic " + java.util.Base64.getEncoder() - .encodeToString( - (PortfolioManagerConfig.getUsername() + ":" - + PortfolioManagerConfig.getPassword()) - .getBytes(StandardCharsets.UTF_8)); - } - - private String requireId(String id, String name) { - if (id == null || id.isBlank()) { - throw new RuntimeException("'" + name + "' is required for dataType: " - + this.dataType); - } - return id; - } - -} diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerProducer.java deleted file mode 100644 index f5d3b27d..00000000 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/PortfolioManagerProducer.java +++ /dev/null @@ -1,131 +0,0 @@ -package ca.concordia.encs.citydata.producers; - -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonObject; - -import ca.concordia.encs.citydata.core.configs.PortfolioManagerConfig; -import ca.concordia.encs.citydata.core.contracts.IProducer; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; -import ca.concordia.encs.citydata.core.utils.RequestOptions; -import ca.concordia.encs.citydata.datastores.InMemoryDataStore; -import ca.concordia.encs.citydata.runners.SingleStepRunner; - -/** - * Producer that fetches meter consumption data from the ENERGY STAR Portfolio Manager API, transforms the XML response, - * and forwards structured results to the CITYdata for potential operations. - * - * @author Minette Zongo - * @since 2026-02-24 - */ - -public class PortfolioManagerProducer extends AbstractProducer implements IProducer { - private String meterId; - private final ArrayList intermediateResult = new ArrayList<>(); - - public void setMeterId(String meterId) { this.meterId = meterId; } - - @SuppressWarnings("unchecked") - private ArrayList getMeterMetadata(SingleStepRunner runner) { - final InMemoryDataStore store = InMemoryDataStore.getInstance(); - final String runnerId = runner.getMetadata("id").toString(); - final IProducer storeResult = store.get(runnerId); - if (storeResult != null) { - return (ArrayList) storeResult.getResult(); - } - return new ArrayList<>(); - } - - private void validateMeterMetadata(ArrayList metadataObject) { - if (metadataObject.isEmpty()) { - throw new RuntimeException("Meter metadata is empty. " - + "Check that meterId '" + this.meterId + "' is valid."); - } - final JsonObject wrapper = metadataObject.getFirst(); - final String xml = wrapper.get("xml").getAsString(); - if (xml.contains("status=\"Error\"")) { - throw new RuntimeException("Portfolio Manager returned an error for meter " - + this.meterId + ": " + xml); - } - } - - private String fetchConsumptionData() { - try { - - final PortfolioManagerMetadataProducer metadataProducer = - new PortfolioManagerMetadataProducer(); - metadataProducer.setMeterId(this.meterId); - metadataProducer.setDataType("METER"); - - final SingleStepRunner internalRunner = new SingleStepRunner(metadataProducer); - final Thread runnerTask = new Thread(() -> { - try { - internalRunner.runSteps(); - while (!internalRunner.isDone()) { - System.out.println("Busy waiting for meter metadata!"); - } - } catch (Exception e) { - internalRunner.setAsDone(); - System.out.println(e.getMessage()); - } - }); - runnerTask.start(); - runnerTask.join(); - - final ArrayList meterMetadata = getMeterMetadata(internalRunner); - validateMeterMetadata(meterMetadata); - - final String consumptionEndpoint = PortfolioManagerConfig.getBaseUrl() - + "/meter/" + this.meterId + "/consumptionData"; - final RequestOptions requestOptions = new RequestOptions(); - requestOptions.setMethod("GET"); - requestOptions.addToHeaders("Authorization", buildBasicAuth()); - requestOptions.addToHeaders("Accept", "application/xml"); - - this.setFilePath(consumptionEndpoint); - this.setFileOptions(requestOptions); - return this.fetchFromPath().toString(); - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Interrupted while fetching meter metadata: " - + e.getMessage(), e); - } - } - - @Override - public void fetch() { - if (this.meterId == null) { - throw new RuntimeException("'meterId' is required. " - + "Use PortfolioManagerMetadataProducer with dataType=METER_LIST " - + "to discover available meter IDs for your property."); - } - - final String xmlResult = fetchConsumptionData(); - - final JsonObject wrapper = new JsonObject(); - wrapper.addProperty("xml", xmlResult); - wrapper.addProperty("meterId", this.meterId); - - this.intermediateResult.add(wrapper); - this.setResult(this.intermediateResult); - this.applyOperation(); - } - - private String buildBasicAuth() { - return "Basic " + java.util.Base64.getEncoder() - .encodeToString( - (PortfolioManagerConfig.getUsername() + ":" - + PortfolioManagerConfig.getPassword()) - .getBytes(StandardCharsets.UTF_8)); - } - -} diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java index 76545d93..d077e146 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RetrofitResultsProducer.java @@ -9,7 +9,6 @@ import ca.concordia.encs.citydata.core.contracts.IOperation; import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; -import ca.concordia.encs.citydata.core.implementations.AbstractProducer; import ca.concordia.encs.citydata.core.implementations.JSONProducer; import ca.concordia.encs.citydata.core.utils.RequestOptions; import ca.concordia.encs.citydata.core.utils.StringUtils; @@ -20,15 +19,17 @@ * This producer fetches data from the HUB API. Credentials are needed to access * this API in your environment variables. * - * @author Gabriel C. Ullmann + * @author Gabriel C. Ullmann, Minette Zongo, Sikandar Ejaz * @since 2025-04-04 */ -//TODO: Intellj keeps showing few trivial warnings here which perhaps have no impact -// on code or in functionality, but it is worth checking later if needed, rather than -// supressing them. +//TODO: Intellj keeps showing few trivial warnings here which perhaps have no impact on code or in functionality, but it is worth checking later if needed, rather than supressing them. -public class RetrofitResultsProducer extends AbstractProducer implements IProducer { +public class RetrofitResultsProducer extends JSONProducer { + + public RetrofitResultsProducer(String filePath) { + super(filePath); + } private JsonArray buildingIds; private IOperation jsonProducerOperation; diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java index 60547a1e..1be86b8e 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/producers/RoomOccupancyProducer.java @@ -10,11 +10,10 @@ /** * This producer reads an sensor data from a CSV file, extracts all data lines, and provides them as input to the potential further * operations - * @author Minette Zongo M. + * @author Minette Zongo M., Sikandar Ejaz * @date: 2025-10-04 */ -//public class RoomOccupancyProducer extends AbstractProducer implements IProducer { public class RoomOccupancyProducer extends CSVProducer { public RoomOccupancyProducer(String filePath) { diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java index 815b6e72..391fd448 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SequentialRunner.java @@ -17,11 +17,11 @@ import ca.concordia.encs.citydata.core.contracts.IRunner; import ca.concordia.encs.citydata.core.exceptions.MiddlewareException; import ca.concordia.encs.citydata.core.implementations.AbstractRunner; +import ca.concordia.encs.citydata.core.implementations.ExceptionProducer; import ca.concordia.encs.citydata.core.utils.ProducerUsageData; import ca.concordia.encs.citydata.core.utils.ReflectionUtils; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; import ca.concordia.encs.citydata.datastores.MongoDataStore; -import ca.concordia.encs.citydata.producers.ExceptionProducer; /*** * This Runner starts with data provided by a producer P1, then applies diff --git a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SingleStepRunner.java b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SingleStepRunner.java index b54a506a..70b5a278 100644 --- a/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SingleStepRunner.java +++ b/Middleware/src/main/java/ca/concordia/encs/citydata/runners/SingleStepRunner.java @@ -10,10 +10,10 @@ import ca.concordia.encs.citydata.core.contracts.IProducer; import ca.concordia.encs.citydata.core.contracts.IRunner; import ca.concordia.encs.citydata.core.implementations.AbstractRunner; +import ca.concordia.encs.citydata.core.implementations.ExceptionProducer; import ca.concordia.encs.citydata.core.utils.ReflectionUtils; import ca.concordia.encs.citydata.core.utils.StringUtils; import ca.concordia.encs.citydata.datastores.InMemoryDataStore; -import ca.concordia.encs.citydata.producers.ExceptionProducer; /** * This Runner executes a single Producer with no Operations. diff --git a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java index a071d5cd..30b78b61 100644 --- a/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java +++ b/Middleware/src/test/java/ca/concordia/encs/citydata/test/producers/ProducersSanityTest.java @@ -9,8 +9,8 @@ import ca.concordia.encs.citydata.operations.StandardFilteringOperation; import ca.concordia.encs.citydata.operations.TemporalAggregationOperation; import ca.concordia.encs.citydata.producers.BuildingProducer; +import ca.concordia.encs.citydata.producers.EnergyConsumptionProducer; import ca.concordia.encs.citydata.producers.EnvironmentalSensorProducer; -import ca.concordia.encs.citydata.producers.OccupancyProducer; import ca.concordia.encs.citydata.producers.RoomOccupancyProducer; public class ProducersSanityTest { @@ -24,26 +24,17 @@ public void testBuildingProducer() { System.out.println(result); } - /* @Test - public void testEnergyConsumptionProducer() { - final EnergyConsumptionProducer producer1 = new EnergyConsumptionProducer(null); - producer1.setCity("montreal"); - producer1.setStartDatetime("2021-09-01 00:00:00"); - producer1.setEndDatetime("2021-09-01 23:59:00"); - producer1.setClientId(1); - producer1.validateParams(); - producer1.buildQuery(); - producer1.fetch(); - ArrayList result = producer1.getResult(); - System.out.println(result); - } - */ @Test - public void testOccupancyProducer() { - final OccupancyProducer producer = new OccupancyProducer(); - producer.setListSize(2); - producer.fetch(); - ArrayList result = producer.getResult(); + public void testEnergyConsumptionProducer() { + final EnergyConsumptionProducer producer1 = new EnergyConsumptionProducer(null); + producer1.setCity("montreal"); + producer1.setStartDatetime("2021-09-01 00:00:00"); + producer1.setEndDatetime("2021-09-01 23:59:00"); + producer1.setClientId(1); + producer1.validateParams(); + producer1.buildQuery(); + producer1.fetch(); + ArrayList result = producer1.getResult(); System.out.println(result); } @@ -80,12 +71,12 @@ public void testRoomOccupancyProducer() { System.out.println(result); } - //@Test - // GeometryProducer wraps JSONProducer and applies MergeOperation - // public void testGeometryProducer() { - // final GeometryProducer producer = new GeometryProducer(); - // producer.setCity("montreal"); - // - // final operation - // } + /* @Test + GeometryProducer wraps JSONProducer and applies MergeOperation + public void testGeometryProducer() { + final GeometryProducer producer = new GeometryProducer(); + producer.setCity("montreal"); + + final operation + }*/ }