Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c72baac
Test that the Cloud Request Engine discovers when it is built, and re…
jwrosewell Nov 9, 2026
a4b6fd5
Discover when the Cloud Request Engine is built, and retry on first u…
jwrosewell Nov 9, 2026
4defa2a
Let a Cloud Request Engine be built from a kept copy of what an earli…
jwrosewell Nov 9, 2026
4f62134
Test that discovery makes both requests at once, times out after 2 se…
jwrosewell Nov 9, 2026
bef79b0
Make the discovery requests at once, time out after 2 seconds by defa…
jwrosewell Nov 9, 2026
be26bcf
Test that the SetHeaders element keeps its configuration once every e…
jwrosewell Nov 9, 2026
9f35ac9
Keep the SetHeaders element's configuration once every element has lo…
jwrosewell Nov 9, 2026
ea5fc04
Give the SetHeaders test mocks their type parameters, as the build tr…
jwrosewell Nov 9, 2026
c309b58
Merge #84's test fix into #86
jwrosewell Nov 9, 2026
8f709b4
Stub the product map once per call, so no varargs array is created
jwrosewell Nov 9, 2026
40452b7
Test that a 408 or 429 at discovery is transient and does not fail th…
jwrosewell Nov 9, 2026
7bc481c
Treat a 408 or 429 at discovery as transient, so it does not fail the…
jwrosewell Nov 9, 2026
726204b
Revert "Treat a 408 or 429 at discovery as transient, so it does not …
jwrosewell Nov 9, 2026
19cc0fa
Revert "Test that a 408 or 429 at discovery is transient and does not…
jwrosewell Nov 9, 2026
1e1dae8
Test that a 408 at discovery fails the build, as every 4xx does
jwrosewell Nov 10, 2026
127b173
Test that a 4xx at discovery fails the build when its body is not JSON
jwrosewell Nov 10, 2026
7d4cebd
Keep the status of a cloud answer whose body is not JSON
jwrosewell Nov 10, 2026
66326a3
Merge rc, which now holds the license key masking, into eager discovery
jwrosewell Nov 10, 2026
5a6fc84
Merge the missing product change at 9fced9b into eager discovery
jwrosewell Nov 10, 2026
8a19907
Merge rc into fix/cloud-request-engine-eager-discovery
jwrosewell Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
*/
public class DeviceDetectionCloudEngine
extends CloudAspectEngineBase<DeviceDataCloud> {
List<AspectPropertyMetaData> aspectProperties;
volatile List<AspectPropertyMetaData> aspectProperties;
private String dataSourceTier;
CloudRequestEngine cloudRequestEngine;
private final Object metadataLock = new Object();

/**
* Construct a new instance of the {@link DeviceDetectionCloudEngine}.
Expand All @@ -72,9 +73,37 @@ public DeviceDetectionCloudEngine(
this.cloudRequestEngine = null;
}

/**
* The property metadata, from the products the Cloud Request Engine
* reports. Until they are known, after a failure at build that can
* pass, this is empty and is read again on the next call.
* @return the property metadata
*/
@Override
public List<AspectPropertyMetaData> getProperties() {
return aspectProperties;
List<AspectPropertyMetaData> loaded = aspectProperties;
if (loaded == null && cloudRequestEngine != null) {
synchronized (metadataLock) {
if (aspectProperties == null) {
loadAspectProperties(cloudRequestEngine);
}
loaded = aspectProperties;
}
}
return loaded == null ?
new ArrayList<AspectPropertyMetaData>() :
loaded;
}

/**
* Whether the property metadata is loaded, which reads it if the Cloud
* Request Engine now knows the accessible properties.
* @return true once the property metadata is loaded
*/
@Override
public boolean hasLoadedProperties() {
getProperties();
return aspectProperties != null;
}

@Override
Expand Down Expand Up @@ -188,9 +217,10 @@ public void addPipeline(Pipeline pipeline) {
if (cloudRequestEngine == null) {
cloudRequestEngine = pipeline.getElement(CloudRequestEngine.class);
if (cloudRequestEngine != null) {
if (loadAspectProperties(cloudRequestEngine) == false) {
logger.error("Failed to load aspect properties");
}
// The products are not known after a failure at build that
// can pass, which the Cloud Request Engine has logged.
// getProperties reads them once they are.
loadAspectProperties(cloudRequestEngine);
}

}
Expand All @@ -209,7 +239,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
findProduct(engine.getPublicProperties());

if (product != null) {
aspectProperties = new ArrayList<>();
List<AspectPropertyMetaData> properties = new ArrayList<>();
dataSourceTier = product.dataTier;

for (AccessiblePropertyMetaData.PropertyMetaData item :
Expand All @@ -221,13 +251,12 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
item.getPropertyType(),
new ArrayList<String>(),
true);
aspectProperties.add(property);
properties.add(property);
}
aspectProperties = properties;
return true;
}
else {
logger.error("Aspect properties could not be loaded for" +
" the Device Detection cloud engine", this);
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,39 @@ public void ProductMissing_PropertyIsMissing() throws Exception {
}
}

/**
* After a failure at build that can pass, the Cloud Request Engine does
* not know the accessible products yet. The engine builds without
* logging anything, and reads its metadata once the products are known
* rather than keeping none.
*/
@Test
public void DiscoveryNotComplete_LoadsTheMetadataLater() throws Exception {
ProductMetaData product = new ProductMetaData();
product.dataTier = "test";
product.properties = Arrays.asList(
new PropertyMetaData("hardwarevendor", "String", "cat", null));
Map<String, ProductMetaData> products = new HashMap<>();
products.put("device", product);
CloudRequestEngine requestEngine = mock(CloudRequestEngine.class);
when(requestEngine.getPublicProperties())
.thenReturn(null)
.thenReturn(products);
Pipeline pipeline = mock(Pipeline.class);
when(pipeline.getElement(CloudRequestEngine.class))
.thenReturn(requestEngine);

try (DeviceDetectionCloudEngine engine =
new DeviceDetectionCloudEngineBuilder(loggerFactory).build()) {
engine.addPipeline(pipeline);
List<String> logged = warningsAndErrors();
assertEquals(logged.toString(), 0, logged.size());

assertEquals(1, engine.getProperties().size());
assertEquals("hardwarevendor", engine.getProperties().get(0).getName());
}
}

/**
* An engine whose Cloud Request Engine reports one product, holding
* one string property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@
*/
public class IPIntelligenceCloudEngine
extends CloudAspectEngineBase<IPIntelligenceDataCloud> {
List<AspectPropertyMetaData> aspectProperties;
volatile List<AspectPropertyMetaData> aspectProperties;
private String dataSourceTier;
private final Object metadataLock = new Object();
// Lower-cased names of properties the cloud returns as weighted lists
// (cloud type "Weighted..."), so processEngine can read their value/weight
// pairs rather than treating them as a plain list.
Expand All @@ -84,9 +85,37 @@ public IPIntelligenceCloudEngine(
this.cloudRequestEngine = null;
}

/**
* The property metadata, from the products the Cloud Request Engine
* reports. Until they are known, after a failure at build that can
* pass, this is empty and is read again on the next call.
* @return the property metadata
*/
@Override
public List<AspectPropertyMetaData> getProperties() {
return aspectProperties;
List<AspectPropertyMetaData> loaded = aspectProperties;
if (loaded == null && cloudRequestEngine != null) {
synchronized (metadataLock) {
if (aspectProperties == null) {
loadAspectProperties(cloudRequestEngine);
}
loaded = aspectProperties;
}
}
return loaded == null ?
new ArrayList<AspectPropertyMetaData>() :
loaded;
}

/**
* Whether the property metadata is loaded, which reads it if the Cloud
* Request Engine now knows the accessible properties.
* @return true once the property metadata is loaded
*/
@Override
public boolean hasLoadedProperties() {
getProperties();
return aspectProperties != null;
}

@Override
Expand Down Expand Up @@ -222,9 +251,10 @@ public void addPipeline(Pipeline pipeline) {
if (cloudRequestEngine == null) {
cloudRequestEngine = pipeline.getElement(CloudRequestEngine.class);
if (cloudRequestEngine != null) {
if (loadAspectProperties(cloudRequestEngine) == false) {
logger.error("Failed to load aspect properties");
}
// The products are not known after a failure at build that
// can pass, which the Cloud Request Engine has logged.
// getProperties reads them once they are.
loadAspectProperties(cloudRequestEngine);
}

}
Expand All @@ -243,7 +273,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
findProduct(engine.getPublicProperties());

if (product != null) {
aspectProperties = new ArrayList<>();
List<AspectPropertyMetaData> properties = new ArrayList<>();
weightedPropertyNames.clear();
dataSourceTier = product.dataTier;

Expand All @@ -259,13 +289,12 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
resolvePropertyType(item),
new ArrayList<String>(),
true);
aspectProperties.add(property);
properties.add(property);
}
aspectProperties = properties;
return true;
}
else {
logger.error("Aspect properties could not be loaded for" +
" the IP Intelligence cloud engine", this);
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,39 @@ public void ProductMissing_PropertyIsMissing() throws Exception {
}
}

/**
* After a failure at build that can pass, the Cloud Request Engine does
* not know the accessible products yet. The engine builds without
* logging anything, and reads its metadata once the products are known
* rather than keeping none.
*/
@Test
public void DiscoveryNotComplete_LoadsTheMetadataLater() throws Exception {
ProductMetaData product = new ProductMetaData();
product.dataTier = "test";
product.properties = Arrays.asList(
new PropertyMetaData("registeredname", "String", "cat", null));
Map<String, ProductMetaData> products = new HashMap<>();
products.put("ip", product);
CloudRequestEngine requestEngine = mock(CloudRequestEngine.class);
when(requestEngine.getPublicProperties())
.thenReturn(null)
.thenReturn(products);
Pipeline pipeline = mock(Pipeline.class);
when(pipeline.getElement(CloudRequestEngine.class))
.thenReturn(requestEngine);

try (IPIntelligenceCloudEngine engine =
new IPIntelligenceCloudEngineBuilder(loggerFactory).build()) {
engine.addPipeline(pipeline);
List<String> logged = warningsAndErrors();
assertEquals(logged.toString(), 0, logged.size());

assertEquals(1, engine.getProperties().size());
assertEquals("registeredname", engine.getProperties().get(0).getName());
}
}

/**
* An engine whose Cloud Request Engine reports one product, holding
* one string property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,44 @@
public class GeoLocationCloudEngine extends CloudAspectEngineBase<CloudGeoData> {

private CloudRequestEngine cloudRequestEngine = null;
private List<AspectPropertyMetaData> properties;
private volatile List<AspectPropertyMetaData> properties;
private final Object metadataLock = new Object();
private String dataSourceTier;
private String elementDataKey;
private EvidenceKeyFilter evidenceKeyFilter = new EvidenceKeyFilterWhitelist(Collections.emptyList());
private String dataProviderPrefix;

/**
* The property metadata, from the products the Cloud Request Engine
* reports. Until they are known, after a failure at build that can
* pass, this is empty and is read again on the next call.
* @return the property metadata
*/
@Override
public List<AspectPropertyMetaData> getProperties() {
return properties;
List<AspectPropertyMetaData> loaded = properties;
if (loaded == null && cloudRequestEngine != null) {
synchronized (metadataLock) {
if (properties == null) {
loadAspectProperties(cloudRequestEngine);
}
loaded = properties;
}
}
return loaded == null ?
new ArrayList<AspectPropertyMetaData>() :
loaded;
}

/**
* Whether the property metadata is loaded, which reads it if the Cloud
* Request Engine now knows the accessible properties.
* @return true once the property metadata is loaded
*/
@Override
public boolean hasLoadedProperties() {
getProperties();
return properties != null;
}

@Override
Expand Down Expand Up @@ -167,9 +196,10 @@ public void addPipeline(Pipeline pipeline) {
if (cloudRequestEngine == null) {
cloudRequestEngine = pipeline.getElement(CloudRequestEngine.class);
if (cloudRequestEngine != null) {
if (loadAspectProperties(cloudRequestEngine) == false) {
logger.error("Failed to load aspect properties");
}
// The products are not known after a failure at build that
// can pass, which the Cloud Request Engine has logged.
// getProperties reads them once they are.
loadAspectProperties(cloudRequestEngine);
}

}
Expand All @@ -181,7 +211,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
findProduct(engine.getPublicProperties());

if (product != null) {
properties = new ArrayList<>();
List<AspectPropertyMetaData> loaded = new ArrayList<>();
dataSourceTier = product.dataTier;

for (AccessiblePropertyMetaData.PropertyMetaData item : product.properties)
Expand All @@ -197,13 +227,12 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
item.delayExecution,
item.evidenceProperties);

properties.add(property);
loaded.add(property);
}
properties = loaded;
return true;
}
else {
logger.error("Aspect properties could not be loaded for" +
" the Location cloud engine", this);
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,40 @@ public void ProductMissing_PropertyIsMissing() throws Exception {
}
}

/**
* After a failure at build that can pass, the Cloud Request Engine does
* not know the accessible products yet. The engine builds without
* logging anything, and reads its metadata once the products are known
* rather than keeping none.
*/
@Test
public void DiscoveryNotComplete_LoadsTheMetadataLater() throws Exception {
ProductMetaData product = new ProductMetaData();
product.dataTier = "test";
product.properties = Arrays.asList(
new PropertyMetaData("town", "String", "cat", null));
Map<String, ProductMetaData> products = new HashMap<>();
products.put("location", product);
CloudRequestEngine requestEngine = mock(CloudRequestEngine.class);
when(requestEngine.getPublicProperties())
.thenReturn(null)
.thenReturn(products);
Pipeline pipeline = mock(Pipeline.class);
when(pipeline.getElement(CloudRequestEngine.class))
.thenReturn(requestEngine);

try (GeoLocationCloudEngine engine =
new GeoLocationCloudEngineBuilder(loggerFactory)
.build(Enums.GeoLocationProvider.FiftyOneDegrees)) {
engine.addPipeline(pipeline);
List<String> logged = warningsAndErrors();
assertEquals(logged.toString(), 0, logged.size());

assertEquals(1, engine.getProperties().size());
assertEquals("town", engine.getProperties().get(0).getName());
}
}

/**
* An engine whose Cloud Request Engine reports one product, holding
* one string property.
Expand Down
Loading
Loading