eoapi-cdk 8.2.3 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsii +130 -66
- package/lib/bastion-host/index.js +1 -1
- package/lib/database/index.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -2
- package/lib/ingestor-api/index.js +1 -1
- package/lib/stac-api/index.js +1 -1
- package/lib/stac-browser/index.js +1 -1
- package/lib/{stac-item-loader → stac-loader}/index.d.ts +83 -57
- package/lib/stac-loader/index.js +284 -0
- package/lib/{stac-item-loader → stac-loader}/runtime/Dockerfile +3 -3
- package/lib/{stac-item-loader → stac-loader}/runtime/pyproject.toml +2 -2
- package/lib/{stac-item-loader/runtime/src/stac_item_loader → stac-loader/runtime/src/stac_loader}/handler.py +54 -26
- package/lib/stactools-item-generator/index.d.ts +2 -2
- package/lib/stactools-item-generator/index.js +3 -3
- package/lib/tipg-api/index.js +1 -1
- package/lib/titiler-pgstac-api/index.js +1 -1
- package/package.json +1 -1
- package/pyproject.toml +4 -4
- package/uv.lock +5 -5
- package/lib/stac-item-loader/index.js +0 -255
- /package/lib/{stac-item-loader/runtime/src/stac_item_loader → stac-loader/runtime/src/stac_loader}/__init__.py +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a, _b;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.StacItemLoader = exports.StacLoader = void 0;
|
|
5
|
+
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
|
|
6
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
7
|
+
const constructs_1 = require("constructs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
/**
|
|
10
|
+
* AWS CDK Construct for STAC Object Loading Infrastructure
|
|
11
|
+
*
|
|
12
|
+
* The StacLoader creates a serverless, event-driven system for loading
|
|
13
|
+
* STAC (SpatioTemporal Asset Catalog) objects into a PostgreSQL database with
|
|
14
|
+
* the pgstac extension. This construct supports multiple ingestion pathways
|
|
15
|
+
* for flexible STAC object loading.
|
|
16
|
+
*
|
|
17
|
+
* ## Architecture Overview
|
|
18
|
+
*
|
|
19
|
+
* This construct creates the following AWS resources:
|
|
20
|
+
* - **SNS Topic**: Entry point for STAC objects and S3 event notifications
|
|
21
|
+
* - **SQS Queue**: Buffers and batches messages before processing (60-second visibility timeout)
|
|
22
|
+
* - **Dead Letter Queue**: Captures failed loading attempts after 5 retries
|
|
23
|
+
* - **Lambda Function**: Python function that processes batches and inserts objects into pgstac
|
|
24
|
+
*
|
|
25
|
+
* ## Data Flow
|
|
26
|
+
*
|
|
27
|
+
* The loader supports two primary data ingestion patterns:
|
|
28
|
+
*
|
|
29
|
+
* ### Direct STAC Object Publishing
|
|
30
|
+
* 1. STAC objects (JSON) are published directly to the SNS topic in message bodies
|
|
31
|
+
* 2. The SQS queue collects messages and batches them (up to {batchSize} objects or 1 minute window)
|
|
32
|
+
* 3. The Lambda function receives batches, validates objects, and inserts into pgstac
|
|
33
|
+
*
|
|
34
|
+
* ### S3 Event-Driven Loading
|
|
35
|
+
* 1. An S3 bucket is configured to send notifications to the SNS topic when json files are created
|
|
36
|
+
* 2. STAC objects are uploaded to S3 buckets as JSON/GeoJSON files
|
|
37
|
+
* 3. S3 event notifications are sent to the SNS topic when objects are uploaded
|
|
38
|
+
* 4. The Lambda function receives S3 events in the SQS message batch, fetches objects from S3, and loads into pgstac
|
|
39
|
+
*
|
|
40
|
+
* ## Batching Behavior
|
|
41
|
+
*
|
|
42
|
+
* The SQS-to-Lambda integration uses intelligent batching to optimize performance:
|
|
43
|
+
*
|
|
44
|
+
* - **Batch Size**: Lambda waits to receive up to `batchSize` messages (default: 500)
|
|
45
|
+
* - **Batching Window**: If fewer than `batchSize` messages are available, Lambda
|
|
46
|
+
* triggers after `maxBatchingWindow` minutes (default: 1 minute)
|
|
47
|
+
* - **Trigger Condition**: Lambda executes when EITHER condition is met first
|
|
48
|
+
* - **Concurrency**: Limited to `maxConcurrency` concurrent executions to prevent database overload
|
|
49
|
+
* - **Partial Failures**: Uses `reportBatchItemFailures` to retry only failed objects
|
|
50
|
+
*
|
|
51
|
+
* This approach balances throughput (larger batches = fewer database connections)
|
|
52
|
+
* with latency (time-based triggers prevent indefinite waiting).
|
|
53
|
+
*
|
|
54
|
+
* ## Error Handling and Dead Letter Queue
|
|
55
|
+
*
|
|
56
|
+
* Failed messages are sent to the dead letter queue after 5 processing attempts.
|
|
57
|
+
* **Important**: This construct provides NO automated handling of dead letter queue
|
|
58
|
+
* messages - monitoring, inspection, and reprocessing of failed objects is the
|
|
59
|
+
* responsibility of the implementing application.
|
|
60
|
+
*
|
|
61
|
+
* Consider implementing:
|
|
62
|
+
* - CloudWatch alarms on dead letter queue depth
|
|
63
|
+
* - Manual or automated reprocessing workflows
|
|
64
|
+
* - Logging and alerting for failed objects
|
|
65
|
+
* - Regular cleanup of old dead letter messages (14-day retention)
|
|
66
|
+
*
|
|
67
|
+
* ## Operational Characteristics
|
|
68
|
+
*
|
|
69
|
+
* - **Scalability**: Lambda scales automatically based on queue depth
|
|
70
|
+
* - **Reliability**: Dead letter queue captures failures for debugging
|
|
71
|
+
* - **Efficiency**: Batching optimizes database operations for high throughput
|
|
72
|
+
* - **Security**: Database credentials accessed via AWS Secrets Manager
|
|
73
|
+
* - **Observability**: CloudWatch logs retained for one week
|
|
74
|
+
*
|
|
75
|
+
* ## Prerequisites
|
|
76
|
+
*
|
|
77
|
+
* Before using this construct, ensure:
|
|
78
|
+
* - The pgstac database has collections loaded (objects require existing collection IDs)
|
|
79
|
+
* - Database credentials are stored in AWS Secrets Manager
|
|
80
|
+
* - The pgstac extension is properly installed and configured
|
|
81
|
+
*
|
|
82
|
+
* ## Usage Example
|
|
83
|
+
*
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Create database first
|
|
86
|
+
* const database = new PgStacDatabase(this, 'Database', {
|
|
87
|
+
* pgstacVersion: '0.9.5'
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* // Create Object loader
|
|
91
|
+
* const loader = new StacLoader(this, 'StacLoader', {
|
|
92
|
+
* pgstacDb: database,
|
|
93
|
+
* batchSize: 1000, // Process up to 1000 objects per batch
|
|
94
|
+
* maxBatchingWindowMinutes: 1, // Wait max 1 minute to fill batch
|
|
95
|
+
* lambdaTimeoutSeconds: 300 // Allow up to 300 seconds for database operations
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* // The topic ARN can be used by other services to publish objects
|
|
99
|
+
* new CfnOutput(this, 'LoaderTopicArn', {
|
|
100
|
+
* value: loader.topic.topicArn
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* ## Direct Object Publishing
|
|
105
|
+
*
|
|
106
|
+
* External services can publish STAC objects directly to the topic:
|
|
107
|
+
*
|
|
108
|
+
* ```bash
|
|
109
|
+
* aws sns publish --topic-arn $STAC_LOAD_TOPIC --message '{
|
|
110
|
+
* "id": "example-collection",
|
|
111
|
+
* "type": "Collection",
|
|
112
|
+
* "title": "Example Collection",
|
|
113
|
+
* "description": "An example collection",
|
|
114
|
+
* "license": "proprietary",
|
|
115
|
+
* "extent": {
|
|
116
|
+
* "spatial": {"bbox": [[-180, -90, 180, 90]]},
|
|
117
|
+
* "temporal": {"interval": [[null, null]]},
|
|
118
|
+
* },
|
|
119
|
+
* "stac_version": "1.1.0",
|
|
120
|
+
* }'
|
|
121
|
+
*
|
|
122
|
+
* aws sns publish --topic-arn $STAC_LOAD_TOPIC --message '{
|
|
123
|
+
* "type": "Feature",
|
|
124
|
+
* "stac_version": "1.0.0",
|
|
125
|
+
* "id": "example-item",
|
|
126
|
+
* "properties": {"datetime": "2021-01-01T00:00:00Z"},
|
|
127
|
+
* "geometry": {"type": "Polygon", "coordinates": [...]},
|
|
128
|
+
* "collection": "example-collection"
|
|
129
|
+
* }'
|
|
130
|
+
*
|
|
131
|
+
*
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* ## S3 Event Configuration
|
|
135
|
+
*
|
|
136
|
+
* To enable S3 event-driven loading, configure S3 bucket notifications to send
|
|
137
|
+
* events to the SNS topic when STAC objects (.json or .geojson files) are uploaded:
|
|
138
|
+
*
|
|
139
|
+
* ```typescript
|
|
140
|
+
* // Configure S3 bucket to send notifications to the loader topic
|
|
141
|
+
* bucket.addEventNotification(
|
|
142
|
+
* s3.EventType.OBJECT_CREATED,
|
|
143
|
+
* new s3n.SnsDestination(loader.topic),
|
|
144
|
+
* { suffix: '.json' }
|
|
145
|
+
* );
|
|
146
|
+
*
|
|
147
|
+
* bucket.addEventNotification(
|
|
148
|
+
* s3.EventType.OBJECT_CREATED,
|
|
149
|
+
* new s3n.SnsDestination(loader.topic),
|
|
150
|
+
* { suffix: '.geojson' }
|
|
151
|
+
* );
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* When STAC objects are uploaded to the configured S3 bucket, the loader will:
|
|
155
|
+
* 1. Receive S3 event notifications via SNS
|
|
156
|
+
* 2. Fetch the STAC JSON from S3
|
|
157
|
+
* 3. Validate and load the objects into the pgstac database
|
|
158
|
+
*
|
|
159
|
+
* ## Monitoring and Troubleshooting
|
|
160
|
+
*
|
|
161
|
+
* - Monitor Lambda logs: `/aws/lambda/{FunctionName}`
|
|
162
|
+
* - **Dead Letter Queue**: Check for failed objects - **no automated handling provided**
|
|
163
|
+
* - Use batch objects failure reporting for partial batch processing
|
|
164
|
+
* - CloudWatch metrics available for queue depth and Lambda performance
|
|
165
|
+
*
|
|
166
|
+
* ### Dead Letter Queue Management
|
|
167
|
+
*
|
|
168
|
+
* Applications must implement their own dead letter queue monitoring:
|
|
169
|
+
*
|
|
170
|
+
* ```typescript
|
|
171
|
+
* // Example: CloudWatch alarm for dead letter queue depth
|
|
172
|
+
* new cloudwatch.Alarm(this, 'DeadLetterAlarm', {
|
|
173
|
+
* metric: loader.deadLetterQueue.metricApproximateNumberOfVisibleMessages(),
|
|
174
|
+
* threshold: 1,
|
|
175
|
+
* evaluationPeriods: 1
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* // Example: Lambda to reprocess dead letter messages
|
|
179
|
+
* const reprocessFunction = new lambda.Function(this, 'Reprocess', {
|
|
180
|
+
* // Implementation to fetch and republish failed messages
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
*/
|
|
185
|
+
class StacLoader extends constructs_1.Construct {
|
|
186
|
+
constructor(scope, id, props) {
|
|
187
|
+
super(scope, id);
|
|
188
|
+
const timeoutSeconds = props.lambdaTimeoutSeconds ?? 300;
|
|
189
|
+
const lambdaRuntime = props.lambdaRuntime ?? aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_11;
|
|
190
|
+
const maxConcurrency = props.maxConcurrency ?? 2;
|
|
191
|
+
// Create dead letter queue
|
|
192
|
+
this.deadLetterQueue = new aws_cdk_lib_1.aws_sqs.Queue(this, "DeadLetterQueue", {
|
|
193
|
+
retentionPeriod: aws_cdk_lib_1.Duration.days(14),
|
|
194
|
+
});
|
|
195
|
+
// Create main queue
|
|
196
|
+
this.queue = new aws_cdk_lib_1.aws_sqs.Queue(this, "Queue", {
|
|
197
|
+
visibilityTimeout: aws_cdk_lib_1.Duration.seconds(timeoutSeconds + 10),
|
|
198
|
+
encryption: aws_cdk_lib_1.aws_sqs.QueueEncryption.SQS_MANAGED,
|
|
199
|
+
deadLetterQueue: {
|
|
200
|
+
maxReceiveCount: 5,
|
|
201
|
+
queue: this.deadLetterQueue,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
// Create SNS topic
|
|
205
|
+
this.topic = new aws_cdk_lib_1.aws_sns.Topic(this, "Topic", {
|
|
206
|
+
displayName: `${id}-StacLoaderTopic`,
|
|
207
|
+
});
|
|
208
|
+
// Subscribe the queue to the topic
|
|
209
|
+
this.topic.addSubscription(new aws_cdk_lib_1.aws_sns_subscriptions.SqsSubscription(this.queue));
|
|
210
|
+
// Create the lambda function
|
|
211
|
+
this.lambdaFunction = new aws_cdk_lib_1.aws_lambda.Function(this, "Function", {
|
|
212
|
+
runtime: lambdaRuntime,
|
|
213
|
+
handler: "stac_loader.handler.handler",
|
|
214
|
+
vpc: props.vpc,
|
|
215
|
+
vpcSubnets: props.subnetSelection,
|
|
216
|
+
code: aws_cdk_lib_1.aws_lambda.Code.fromDockerBuild(path.join(__dirname, ".."), {
|
|
217
|
+
file: "stac-loader/runtime/Dockerfile",
|
|
218
|
+
platform: "linux/amd64",
|
|
219
|
+
buildArgs: {
|
|
220
|
+
PYTHON_VERSION: lambdaRuntime.toString().replace("python", ""),
|
|
221
|
+
PGSTAC_VERSION: props.pgstacDb.pgstacVersion,
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
memorySize: props.memorySize ?? 1024,
|
|
225
|
+
timeout: aws_cdk_lib_1.Duration.seconds(timeoutSeconds),
|
|
226
|
+
reservedConcurrentExecutions: maxConcurrency,
|
|
227
|
+
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_WEEK,
|
|
228
|
+
environment: {
|
|
229
|
+
PGSTAC_SECRET_ARN: props.pgstacDb.pgstacSecret.secretArn,
|
|
230
|
+
...props.environment,
|
|
231
|
+
},
|
|
232
|
+
// overwrites defaults with user-provided configurable properties
|
|
233
|
+
...props.lambdaFunctionOptions,
|
|
234
|
+
});
|
|
235
|
+
// Grant permissions to read the database secret
|
|
236
|
+
props.pgstacDb.pgstacSecret.grantRead(this.lambdaFunction);
|
|
237
|
+
// Add SQS event source to the lambda
|
|
238
|
+
this.lambdaFunction.addEventSource(new aws_cdk_lib_1.aws_lambda_event_sources.SqsEventSource(this.queue, {
|
|
239
|
+
batchSize: props.batchSize ?? 500,
|
|
240
|
+
maxBatchingWindow: aws_cdk_lib_1.Duration.minutes(props.maxBatchingWindowMinutes ?? 1),
|
|
241
|
+
maxConcurrency: maxConcurrency,
|
|
242
|
+
reportBatchItemFailures: true,
|
|
243
|
+
}));
|
|
244
|
+
// Create outputs
|
|
245
|
+
const exportPrefix = aws_cdk_lib_1.Stack.of(this).stackName;
|
|
246
|
+
new aws_cdk_lib_1.CfnOutput(this, "TopicArn", {
|
|
247
|
+
value: this.topic.topicArn,
|
|
248
|
+
description: "ARN of the StacLoader SNS Topic",
|
|
249
|
+
exportName: `${exportPrefix}-stac-loader-topic-arn`,
|
|
250
|
+
});
|
|
251
|
+
new aws_cdk_lib_1.CfnOutput(this, "QueueUrl", {
|
|
252
|
+
value: this.queue.queueUrl,
|
|
253
|
+
description: "URL of the StacLoader SQS Queue",
|
|
254
|
+
exportName: `${exportPrefix}-stac-loader-queue-url`,
|
|
255
|
+
});
|
|
256
|
+
new aws_cdk_lib_1.CfnOutput(this, "DeadLetterQueueUrl", {
|
|
257
|
+
value: this.deadLetterQueue.queueUrl,
|
|
258
|
+
description: "URL of the StacLoader Dead Letter Queue",
|
|
259
|
+
exportName: `${exportPrefix}-stac-loader-deadletter-queue-url`,
|
|
260
|
+
});
|
|
261
|
+
new aws_cdk_lib_1.CfnOutput(this, "FunctionName", {
|
|
262
|
+
value: this.lambdaFunction.functionName,
|
|
263
|
+
description: "Name of the StacLoader Lambda Function",
|
|
264
|
+
exportName: `${exportPrefix}-stac-loader-function-name`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
exports.StacLoader = StacLoader;
|
|
269
|
+
_a = JSII_RTTI_SYMBOL_1;
|
|
270
|
+
StacLoader[_a] = { fqn: "eoapi-cdk.StacLoader", version: "8.3.1" };
|
|
271
|
+
/**
|
|
272
|
+
* @deprecated Use StacLoader instead. StacItemLoader will be removed in a future version.
|
|
273
|
+
*/
|
|
274
|
+
class StacItemLoader extends StacLoader {
|
|
275
|
+
constructor(scope, id, props) {
|
|
276
|
+
console.warn(`StacItemLoader is deprecated. Please use StacLoader instead. ` +
|
|
277
|
+
`StacItemLoader will be removed in a future version.`);
|
|
278
|
+
super(scope, id, props);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.StacItemLoader = StacItemLoader;
|
|
282
|
+
_b = JSII_RTTI_SYMBOL_1;
|
|
283
|
+
StacItemLoader[_b] = { fqn: "eoapi-cdk.StacItemLoader", version: "8.3.1" };
|
|
284
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDZDQVdxQjtBQUNyQiwyQ0FBdUM7QUFFdkMsNkJBQTZCO0FBMEk3Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQStLRztBQUNILE1BQWEsVUFBVyxTQUFRLHNCQUFTO0lBb0R2QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXNCO1FBQzlELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxjQUFjLEdBQUcsS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQztRQUN6RCxNQUFNLGFBQWEsR0FBRyxLQUFLLENBQUMsYUFBYSxJQUFJLHdCQUFNLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztRQUN4RSxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsY0FBYyxJQUFJLENBQUMsQ0FBQztRQUVqRCwyQkFBMkI7UUFDM0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLHFCQUFHLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxpQkFBaUIsRUFBRTtZQUM1RCxlQUFlLEVBQUUsc0JBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1NBQ25DLENBQUMsQ0FBQztRQUVILG9CQUFvQjtRQUNwQixJQUFJLENBQUMsS0FBSyxHQUFHLElBQUkscUJBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRTtZQUN4QyxpQkFBaUIsRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEdBQUcsRUFBRSxDQUFDO1lB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QUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRO1lBQzFCLFdBQVcsRUFBRSxpQ0FBaUM7WUFDOUMsVUFBVSxFQUFFLEdBQUcsWUFBWSx3QkFBd0I7U0FDcEQsQ0FBQyxDQUFDO1FBRUgsSUFBSSx1QkFBUyxDQUFDLElBQUksRUFBRSxvQkFBb0IsRUFBRTtZQUN4QyxLQUFLLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxRQUFRO1lBQ3BDLFdBQVcsRUFBRSx5Q0FBeUM7WUFDdEQsVUFBVSxFQUFFLEdBQUcsWUFBWSxtQ0FBbUM7U0FDL0QsQ0FBQyxDQUFDO1FBRUgsSUFBSSx1QkFBUyxDQUFDLElBQUksRUFBRSxjQUFjLEVBQUU7WUFDbEMsS0FBSyxFQUFFLElBQUksQ0FBQyxjQUFjLENBQUMsWUFBWTtZQUN2QyxXQUFXLEVBQUUsd0NBQXdDO1lBQ3JELFVBQVUsRUFBRSxHQUFHLFlBQVksNEJBQTRCO1NBQ3hELENBQUMsQ0FBQztJQUNMLENBQUM7O0FBdEpILGdDQXVKQzs7O0FBRUQ7O0dBRUc7QUFDSCxNQUFhLGNBQWUsU0FBUSxVQUFVO0lBQzVDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBc0I7UUFDOUQsT0FBTyxDQUFDLElBQUksQ0FDViwrREFBK0Q7WUFDN0QscURBQXFELENBQ3hELENBQUM7UUFFRixLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMxQixDQUFDOztBQVJILHdDQVNDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgYXdzX2VjMiBhcyBlYzIsXG4gIGF3c19sYW1iZGEgYXMgbGFtYmRhLFxuICBhd3Nfc3FzIGFzIHNxcyxcbiAgYXdzX3NucyBhcyBzbnMsXG4gIGF3c19zbnNfc3Vic2NyaXB0aW9ucyBhcyBzbnNTdWJzY3JpcHRpb25zLFxuICBhd3NfbGFtYmRhX2V2ZW50X3NvdXJjZXMgYXMgbGFtYmRhRXZlbnRTb3VyY2VzLFxuICBhd3NfbG9ncyBhcyBsb2dzLFxuICBEdXJhdGlvbixcbiAgQ2ZuT3V0cHV0LFxuICBTdGFjayxcbn0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0IHsgUGdTdGFjRGF0YWJhc2UgfSBmcm9tIFwiLi4vZGF0YWJhc2VcIjtcbmltcG9ydCAqIGFzIHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IEN1c3RvbUxhbWJkYUZ1bmN0aW9uUHJvcHMgfSBmcm9tIFwiLi4vdXRpbHNcIjtcblxuLyoqXG4gKiBDb25maWd1cmF0aW9uIHByb3BlcnRpZXMgZm9yIHRoZSBTdGFjTG9hZGVyIGNvbnN0cnVjdC5cbiAqXG4gKiBUaGUgU3RhY0xvYWRlciBpcyBwYXJ0IG9mIGEgdHdvLXBoYXNlIHNlcnZlcmxlc3MgU1RBQyBpbmdlc3Rpb24gcGlwZWxpbmVcbiAqIHRoYXQgbG9hZHMgU1RBQyBjb2xsZWN0aW9ucyBhbmQgaXRlbXMgaW50byBhIHBnc3RhYyBkYXRhYmFzZS4gVGhpcyBjb25zdHJ1Y3QgY3JlYXRlc1xuICogdGhlIGluZnJhc3RydWN0dXJlIGZvciByZWNlaXZpbmcgU1RBQyBvYmplY3RzIGZyb20gbXVsdGlwbGUgc291cmNlczpcbiAqIDEuIFNOUyBtZXNzYWdlcyBjb250YWluaW5nIFNUQUMgbWV0YWRhdGEgKGRpcmVjdCBpbmdlc3Rpb24pXG4gKiAyLiBTMyBldmVudCBub3RpZmljYXRpb25zIGZvciBTVEFDIG9iamVjdHMgdXBsb2FkZWQgdG8gUzMgYnVja2V0c1xuICpcbiAqIE9iamVjdHMgZnJvbSBib3RoIHNvdXJjZXMgYXJlIGJhdGNoZWQgYW5kIGluc2VydGVkIGludG8gUG9zdGdyZVNRTCB3aXRoIHRoZSBwZ3N0YWMgZXh0ZW5zaW9uLlxuICpcbiAqIEBleGFtcGxlXG4gKiBjb25zdCBsb2FkZXIgPSBuZXcgU3RhY0xvYWRlcih0aGlzLCAnU3RhY0xvYWRlcicsIHtcbiAqICAgcGdzdGFjRGI6IGRhdGFiYXNlLFxuICogICBiYXRjaFNpemU6IDEwMDAsXG4gKiAgIG1heEJhdGNoaW5nV2luZG93TWludXRlczogMSxcbiAqICAgbGFtYmRhVGltZW91dFNlY29uZHM6IDMwMFxuICogfSk7XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3RhY0xvYWRlclByb3BzIHtcbiAgLyoqXG4gICAqIFRoZSBQZ1NUQUMgZGF0YWJhc2UgaW5zdGFuY2UgdG8gbG9hZCBkYXRhIGludG8uXG4gICAqXG4gICAqIFRoaXMgZGF0YWJhc2UgbXVzdCBoYXZlIHRoZSBwZ3N0YWMgZXh0ZW5zaW9uIGluc3RhbGxlZCBhbmQgYmUgcHJvcGVybHlcbiAgICogY29uZmlndXJlZCB3aXRoIGNvbGxlY3Rpb25zIGJlZm9yZSBvYmplY3RzIGNhbiBiZSBsb2FkZWQuIFRoZSBsb2FkZXIgd2lsbFxuICAgKiB1c2UgQVdTIFNlY3JldHMgTWFuYWdlciB0byBzZWN1cmVseSBhY2Nlc3MgZGF0YWJhc2UgY3JlZGVudGlhbHMuXG4gICAqL1xuICByZWFkb25seSBwZ3N0YWNEYjogUGdTdGFjRGF0YWJhc2U7XG5cbiAgLyoqXG4gICAqIFZQQyBpbnRvIHdoaWNoIHRoZSBsYW1iZGEgc2hvdWxkIGJlIGRlcGxveWVkLlxuICAgKi9cbiAgcmVhZG9ubHkgdnBjPzogZWMyLklWcGM7XG5cbiAgLyoqXG4gICAqIFN1Ym5ldCBpbnRvIHdoaWNoIHRoZSBsYW1iZGEgc2hvdWxkIGJlIGRlcGxveWVkLlxuICAgKi9cbiAgcmVhZG9ubHkgc3VibmV0U2VsZWN0aW9uPzogZWMyLlN1Ym5ldFNlbGVjdGlvbjtcblxuICAvKipcbiAgICogVGhlIGxhbWJkYSBydW50aW1lIHRvIHVzZSBmb3IgdGhlIGl0ZW0gbG9hZGluZyBmdW5jdGlvbi5cbiAgICpcbiAgICogVGhlIGZ1bmN0aW9uIGlzIGltcGxlbWVudGVkIGluIFB5dGhvbiBhbmQgdXNlcyBweXBnc3RhYyBmb3IgZGF0YWJhc2VcbiAgICogb3BlcmF0aW9ucy4gRW5zdXJlIHRoZSBydW50aW1lIHZlcnNpb24gaXMgY29tcGF0aWJsZSB3aXRoIHRoZSBwZ3N0YWNcbiAgICogdmVyc2lvbiBzcGVjaWZpZWQgaW4gdGhlIGRhdGFiYXNlIGNvbmZpZ3VyYXRpb24uXG4gICAqXG4gICAqIEBkZWZhdWx0IGxhbWJkYS5SdW50aW1lLlBZVEhPTl8zXzExXG4gICAqL1xuICByZWFkb25seSBsYW1iZGFSdW50aW1lPzogbGFtYmRhLlJ1bnRpbWU7XG5cbiAgLyoqXG4gICAqIFRoZSB0aW1lb3V0IGZvciB0aGUgaXRlbSBsb2FkIGxhbWJkYSBpbiBzZWNvbmRzLlxuICAgKlxuICAgKiBUaGlzIHNob3VsZCBhY2NvbW1vZGF0ZSB0aGUgdGltZSBuZWVkZWQgdG8gcHJvY2VzcyB1cCB0byBgYmF0Y2hTaXplYFxuICAgKiBvYmplY3RzIGFuZCBwZXJmb3JtIGRhdGFiYXNlIGluc2VydGlvbnMuIFRoZSBTUVMgdmlzaWJpbGl0eSB0aW1lb3V0XG4gICAqIHdpbGwgYmUgc2V0IHRvIHRoaXMgdmFsdWUgcGx1cyAxMCBzZWNvbmRzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAzMDBcbiAgICovXG4gIHJlYWRvbmx5IGxhbWJkYVRpbWVvdXRTZWNvbmRzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNZW1vcnkgc2l6ZSBmb3IgdGhlIGxhbWJkYSBmdW5jdGlvbiBpbiBNQi5cbiAgICpcbiAgICogSGlnaGVyIG1lbW9yeSBhbGxvY2F0aW9uIG1heSBpbXByb3ZlIHBlcmZvcm1hbmNlIHdoZW4gcHJvY2Vzc2luZ1xuICAgKiBsYXJnZSBiYXRjaGVzIG9mIFNUQUMgb2JqZWN0cywgZXNwZWNpYWxseSBmb3IgbWVtb3J5LWludGVuc2l2ZVxuICAgKiBkYXRhYmFzZSBvcGVyYXRpb25zLlxuICAgKlxuICAgKiBAZGVmYXVsdCAxMDI0XG4gICAqL1xuICByZWFkb25seSBtZW1vcnlTaXplPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBTUVMgYmF0Y2ggc2l6ZSBmb3IgbGFtYmRhIGV2ZW50IHNvdXJjZS5cbiAgICpcbiAgICogVGhpcyBkZXRlcm1pbmVzIHRoZSBtYXhpbXVtIG51bWJlciBvZiBTVEFDIG9iamVjdHMgdGhhdCB3aWxsIGJlXG4gICAqIHByb2Nlc3NlZCB0b2dldGhlciBpbiBhIHNpbmdsZSBsYW1iZGEgaW52b2NhdGlvbi4gTGFyZ2VyIGJhdGNoXG4gICAqIHNpemVzIGltcHJvdmUgZGF0YWJhc2UgaW5zZXJ0aW9uIGVmZmljaWVuY3kgYnV0IHJlcXVpcmUgbW9yZVxuICAgKiBtZW1vcnkgYW5kIGxvbmdlciBwcm9jZXNzaW5nIHRpbWUuXG4gICAqXG4gICAqICoqQmF0Y2hpbmcgQmVoYXZpb3IqKjogU1FTIHdpbGwgd2FpdCB0byBhY2N1bXVsYXRlIHVwIHRvIHRoaXMgbWFueVxuICAgKiBtZXNzYWdlcyBiZWZvcmUgdHJpZ2dlcmluZyB0aGUgTGFtYmRhLCBPUiB1bnRpbCB0aGUgbWF4QmF0Y2hpbmdXaW5kb3dcbiAgICogdGltZW91dCBpcyByZWFjaGVkLCB3aGljaGV2ZXIgY29tZXMgZmlyc3QuIFRoaXMgY3JlYXRlcyBhbiBlZmZpY2llbnRcbiAgICogYmFsYW5jZSBiZXR3ZWVuIHRocm91Z2hwdXQgYW5kIGxhdGVuY3kuXG4gICAqXG4gICAqIEBkZWZhdWx0IDUwMFxuICAgKi9cbiAgcmVhZG9ubHkgYmF0Y2hTaXplPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNYXhpbXVtIGJhdGNoaW5nIHdpbmRvdyBpbiBtaW51dGVzLlxuICAgKlxuICAgKiBFdmVuIGlmIHRoZSBiYXRjaCBzaXplIGlzbid0IHJlYWNoZWQsIHRoZSBsYW1iZGEgd2lsbCBiZSB0cmlnZ2VyZWRcbiAgICogYWZ0ZXIgdGhpcyB0aW1lIHBlcmlvZCB0byBlbnN1cmUgdGltZWx5IHByb2Nlc3Npbmcgb2Ygb2JqZWN0cy5cbiAgICogVGhpcyBwcmV2ZW50cyBvYmplY3RzIGZyb20gd2FpdGluZyBpbmRlZmluaXRlbHkgaW4gbG93LXZvbHVtZSBzY2VuYXJpb3MuXG4gICAqXG4gICAqICoqSW1wb3J0YW50Kio6IFRoaXMgdGltZW91dCB3b3JrcyBpbiBjb25qdW5jdGlvbiB3aXRoIGJhdGNoU2l6ZSAtIFNRU1xuICAgKiB3aWxsIHRyaWdnZXIgdGhlIExhbWJkYSB3aGVuIEVJVEhFUiB0aGUgYmF0Y2ggc2l6ZSBpcyByZWFjaGVkIE9SIHRoaXNcbiAgICogdGltZSB3aW5kb3cgZXhwaXJlcywgZW5zdXJpbmcgb2JqZWN0cyBhcmUgcHJvY2Vzc2VkIGluIGEgdGltZWx5IG1hbm5lclxuICAgKiByZWdhcmRsZXNzIG9mIHZvbHVtZS5cbiAgICpcbiAgICogQGRlZmF1bHQgMVxuICAgKi9cbiAgcmVhZG9ubHkgbWF4QmF0Y2hpbmdXaW5kb3dNaW51dGVzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNYXhpbXVtIGNvbmN1cnJlbnQgZXhlY3V0aW9ucyBmb3IgdGhlIFN0YWNMb2FkZXIgTGFtYmRhIGZ1bmN0aW9uXG4gICAqXG4gICAqIFRoaXMgbGltaXQgd2lsbCBiZSBhcHBsaWVkIHRvIHRoZSBMYW1iZGEgZnVuY3Rpb24gYW5kIHdpbGwgY29udHJvbCBob3dcbiAgICogbWFueSBjb25jdXJyZW50IGJhdGNoZXMgd2lsbCBiZSByZWxlYXNlZCBmcm9tIHRoZSBTUVMgcXVldWUuXG4gICAqXG4gICAqIEBkZWZhdWx0IDJcbiAgICovXG4gIHJlYWRvbmx5IG1heENvbmN1cnJlbmN5PzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBBZGRpdGlvbmFsIGVudmlyb25tZW50IHZhcmlhYmxlcyBmb3IgdGhlIGxhbWJkYSBmdW5jdGlvbi5cbiAgICpcbiAgICogVGhlc2Ugd2lsbCBiZSBtZXJnZWQgd2l0aCB0aGUgZGVmYXVsdCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgaW5jbHVkaW5nXG4gICAqIFBHU1RBQ19TRUNSRVRfQVJOLiBVc2UgdGhpcyBmb3IgY3VzdG9tIGNvbmZpZ3VyYXRpb24gb3IgZGVidWdnaW5nIGZsYWdzLlxuICAgKlxuICAgKiBJZiB5b3Ugd2FudCB0byBlbmFibGUgdGhlIG9wdGlvbiB0byB1cGxvYWQgYSBib2lsZXJwbGF0ZSBjb2xsZWN0aW9uIHJlY29yZFxuICAgKiBpbiB0aGUgZXZlbnQgdGhhdCB0aGUgY29sbGVjdGlvbiByZWNvcmQgZG9lcyBub3QgeWV0IGV4aXN0IGZvciBhbiBpdGVtIHRoYXRcbiAgICogaXMgc2V0IHRvIGJlIGxvYWRlZCwgc2V0IHRoZSB2YXJpYWJsZSBgXCJDUkVBVEVfQ09MTEVDVElPTlNfSUZfTUlTU0lOR1wiOiBcIlRSVUVcImAuXG4gICAqL1xuICByZWFkb25seSBlbnZpcm9ubWVudD86IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH07XG5cbiAgLyoqXG4gICAqIENhbiBiZSB1c2VkIHRvIG92ZXJyaWRlIHRoZSBkZWZhdWx0IGxhbWJkYSBmdW5jdGlvbiBwcm9wZXJ0aWVzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIGRlZmluZWQgaW4gdGhlIGNvbnN0cnVjdC5cbiAgICovXG4gIHJlYWRvbmx5IGxhbWJkYUZ1bmN0aW9uT3B0aW9ucz86IEN1c3RvbUxhbWJkYUZ1bmN0aW9uUHJvcHM7XG59XG5cbi8qKlxuICogQVdTIENESyBDb25zdHJ1Y3QgZm9yIFNUQUMgT2JqZWN0IExvYWRpbmcgSW5mcmFzdHJ1Y3R1cmVcbiAqXG4gKiBUaGUgU3RhY0xvYWRlciBjcmVhdGVzIGEgc2VydmVybGVzcywgZXZlbnQtZHJpdmVuIHN5c3RlbSBmb3IgbG9hZGluZ1xuICogU1RBQyAoU3BhdGlvVGVtcG9yYWwgQXNzZXQgQ2F0YWxvZykgb2JqZWN0cyBpbnRvIGEgUG9zdGdyZVNRTCBkYXRhYmFzZSB3aXRoXG4gKiB0aGUgcGdzdGFjIGV4dGVuc2lvbi4gVGhpcyBjb25zdHJ1Y3Qgc3VwcG9ydHMgbXVsdGlwbGUgaW5nZXN0aW9uIHBhdGh3YXlzXG4gKiBmb3IgZmxleGlibGUgU1RBQyBvYmplY3QgbG9hZGluZy5cbiAqXG4gKiAjIyBBcmNoaXRlY3R1cmUgT3ZlcnZpZXdcbiAqXG4gKiBUaGlzIGNvbnN0cnVjdCBjcmVhdGVzIHRoZSBmb2xsb3dpbmcgQVdTIHJlc291cmNlczpcbiAqIC0gKipTTlMgVG9waWMqKjogRW50cnkgcG9pbnQgZm9yIFNUQUMgb2JqZWN0cyBhbmQgUzMgZXZlbnQgbm90aWZpY2F0aW9uc1xuICogLSAqKlNRUyBRdWV1ZSoqOiBCdWZmZXJzIGFuZCBiYXRjaGVzIG1lc3NhZ2VzIGJlZm9yZSBwcm9jZXNzaW5nICg2MC1zZWNvbmQgdmlzaWJpbGl0eSB0aW1lb3V0KVxuICogLSAqKkRlYWQgTGV0dGVyIFF1ZXVlKio6IENhcHR1cmVzIGZhaWxlZCBsb2FkaW5nIGF0dGVtcHRzIGFmdGVyIDUgcmV0cmllc1xuICogLSAqKkxhbWJkYSBGdW5jdGlvbioqOiBQeXRob24gZnVuY3Rpb24gdGhhdCBwcm9jZXNzZXMgYmF0Y2hlcyBhbmQgaW5zZXJ0cyBvYmplY3RzIGludG8gcGdzdGFjXG4gKlxuICogIyMgRGF0YSBGbG93XG4gKlxuICogVGhlIGxvYWRlciBzdXBwb3J0cyB0d28gcHJpbWFyeSBkYXRhIGluZ2VzdGlvbiBwYXR0ZXJuczpcbiAqXG4gKiAjIyMgRGlyZWN0IFNUQUMgT2JqZWN0IFB1Ymxpc2hpbmdcbiAqIDEuIFNUQUMgb2JqZWN0cyAoSlNPTikgYXJlIHB1Ymxpc2hlZCBkaXJlY3RseSB0byB0aGUgU05TIHRvcGljIGluIG1lc3NhZ2UgYm9kaWVzXG4gKiAyLiBUaGUgU1FTIHF1ZXVlIGNvbGxlY3RzIG1lc3NhZ2VzIGFuZCBiYXRjaGVzIHRoZW0gKHVwIHRvIHtiYXRjaFNpemV9IG9iamVjdHMgb3IgMSBtaW51dGUgd2luZG93KVxuICogMy4gVGhlIExhbWJkYSBmdW5jdGlvbiByZWNlaXZlcyBiYXRjaGVzLCB2YWxpZGF0ZXMgb2JqZWN0cywgYW5kIGluc2VydHMgaW50byBwZ3N0YWNcbiAqXG4gKiAjIyMgUzMgRXZlbnQtRHJpdmVuIExvYWRpbmdcbiAqIDEuIEFuIFMzIGJ1Y2tldCBpcyBjb25maWd1cmVkIHRvIHNlbmQgbm90aWZpY2F0aW9ucyB0byB0aGUgU05TIHRvcGljIHdoZW4ganNvbiBmaWxlcyBhcmUgY3JlYXRlZFxuICogMi4gU1RBQyBvYmplY3RzIGFyZSB1cGxvYWRlZCB0byBTMyBidWNrZXRzIGFzIEpTT04vR2VvSlNPTiBmaWxlc1xuICogMy4gUzMgZXZlbnQgbm90aWZpY2F0aW9ucyBhcmUgc2VudCB0byB0aGUgU05TIHRvcGljIHdoZW4gb2JqZWN0cyBhcmUgdXBsb2FkZWRcbiAqIDQuIFRoZSBMYW1iZGEgZnVuY3Rpb24gcmVjZWl2ZXMgUzMgZXZlbnRzIGluIHRoZSBTUVMgbWVzc2FnZSBiYXRjaCwgZmV0Y2hlcyBvYmplY3RzIGZyb20gUzMsIGFuZCBsb2FkcyBpbnRvIHBnc3RhY1xuICpcbiAqICMjIEJhdGNoaW5nIEJlaGF2aW9yXG4gKlxuICogVGhlIFNRUy10by1MYW1iZGEgaW50ZWdyYXRpb24gdXNlcyBpbnRlbGxpZ2VudCBiYXRjaGluZyB0byBvcHRpbWl6ZSBwZXJmb3JtYW5jZTpcbiAqXG4gKiAtICoqQmF0Y2ggU2l6ZSoqOiBMYW1iZGEgd2FpdHMgdG8gcmVjZWl2ZSB1cCB0byBgYmF0Y2hTaXplYCBtZXNzYWdlcyAoZGVmYXVsdDogNTAwKVxuICogLSAqKkJhdGNoaW5nIFdpbmRvdyoqOiBJZiBmZXdlciB0aGFuIGBiYXRjaFNpemVgIG1lc3NhZ2VzIGFyZSBhdmFpbGFibGUsIExhbWJkYVxuICogICB0cmlnZ2VycyBhZnRlciBgbWF4QmF0Y2hpbmdXaW5kb3dgIG1pbnV0ZXMgKGRlZmF1bHQ6IDEgbWludXRlKVxuICogLSAqKlRyaWdnZXIgQ29uZGl0aW9uKio6IExhbWJkYSBleGVjdXRlcyB3aGVuIEVJVEhFUiBjb25kaXRpb24gaXMgbWV0IGZpcnN0XG4gKiAtICoqQ29uY3VycmVuY3kqKjogTGltaXRlZCB0byBgbWF4Q29uY3VycmVuY3lgIGNvbmN1cnJlbnQgZXhlY3V0aW9ucyB0byBwcmV2ZW50IGRhdGFiYXNlIG92ZXJsb2FkXG4gKiAtICoqUGFydGlhbCBGYWlsdXJlcyoqOiBVc2VzIGByZXBvcnRCYXRjaEl0ZW1GYWlsdXJlc2AgdG8gcmV0cnkgb25seSBmYWlsZWQgb2JqZWN0c1xuICpcbiAqIFRoaXMgYXBwcm9hY2ggYmFsYW5jZXMgdGhyb3VnaHB1dCAobGFyZ2VyIGJhdGNoZXMgPSBmZXdlciBkYXRhYmFzZSBjb25uZWN0aW9ucylcbiAqIHdpdGggbGF0ZW5jeSAodGltZS1iYXNlZCB0cmlnZ2VycyBwcmV2ZW50IGluZGVmaW5pdGUgd2FpdGluZykuXG4gKlxuICogIyMgRXJyb3IgSGFuZGxpbmcgYW5kIERlYWQgTGV0dGVyIFF1ZXVlXG4gKlxuICogRmFpbGVkIG1lc3NhZ2VzIGFyZSBzZW50IHRvIHRoZSBkZWFkIGxldHRlciBxdWV1ZSBhZnRlciA1IHByb2Nlc3NpbmcgYXR0ZW1wdHMuXG4gKiAqKkltcG9ydGFudCoqOiBUaGlzIGNvbnN0cnVjdCBwcm92aWRlcyBOTyBhdXRvbWF0ZWQgaGFuZGxpbmcgb2YgZGVhZCBsZXR0ZXIgcXVldWVcbiAqIG1lc3NhZ2VzIC0gbW9uaXRvcmluZywgaW5zcGVjdGlvbiwgYW5kIHJlcHJvY2Vzc2luZyBvZiBmYWlsZWQgb2JqZWN0cyBpcyB0aGVcbiAqIHJlc3BvbnNpYmlsaXR5IG9mIHRoZSBpbXBsZW1lbnRpbmcgYXBwbGljYXRpb24uXG4gKlxuICogQ29uc2lkZXIgaW1wbGVtZW50aW5nOlxuICogLSBDbG91ZFdhdGNoIGFsYXJtcyBvbiBkZWFkIGxldHRlciBxdWV1ZSBkZXB0aFxuICogLSBNYW51YWwgb3IgYXV0b21hdGVkIHJlcHJvY2Vzc2luZyB3b3JrZmxvd3NcbiAqIC0gTG9nZ2luZyBhbmQgYWxlcnRpbmcgZm9yIGZhaWxlZCBvYmplY3RzXG4gKiAtIFJlZ3VsYXIgY2xlYW51cCBvZiBvbGQgZGVhZCBsZXR0ZXIgbWVzc2FnZXMgKDE0LWRheSByZXRlbnRpb24pXG4gKlxuICogIyMgT3BlcmF0aW9uYWwgQ2hhcmFjdGVyaXN0aWNzXG4gKlxuICogLSAqKlNjYWxhYmlsaXR5Kio6IExhbWJkYSBzY2FsZXMgYXV0b21hdGljYWxseSBiYXNlZCBvbiBxdWV1ZSBkZXB0aFxuICogLSAqKlJlbGlhYmlsaXR5Kio6IERlYWQgbGV0dGVyIHF1ZXVlIGNhcHR1cmVzIGZhaWx1cmVzIGZvciBkZWJ1Z2dpbmdcbiAqIC0gKipFZmZpY2llbmN5Kio6IEJhdGNoaW5nIG9wdGltaXplcyBkYXRhYmFzZSBvcGVyYXRpb25zIGZvciBoaWdoIHRocm91Z2hwdXRcbiAqIC0gKipTZWN1cml0eSoqOiBEYXRhYmFzZSBjcmVkZW50aWFscyBhY2Nlc3NlZCB2aWEgQVdTIFNlY3JldHMgTWFuYWdlclxuICogLSAqKk9ic2VydmFiaWxpdHkqKjogQ2xvdWRXYXRjaCBsb2dzIHJldGFpbmVkIGZvciBvbmUgd2Vla1xuICpcbiAqICMjIFByZXJlcXVpc2l0ZXNcbiAqXG4gKiBCZWZvcmUgdXNpbmcgdGhpcyBjb25zdHJ1Y3QsIGVuc3VyZTpcbiAqIC0gVGhlIHBnc3RhYyBkYXRhYmFzZSBoYXMgY29sbGVjdGlvbnMgbG9hZGVkIChvYmplY3RzIHJlcXVpcmUgZXhpc3RpbmcgY29sbGVjdGlvbiBJRHMpXG4gKiAtIERhdGFiYXNlIGNyZWRlbnRpYWxzIGFyZSBzdG9yZWQgaW4gQVdTIFNlY3JldHMgTWFuYWdlclxuICogLSBUaGUgcGdzdGFjIGV4dGVuc2lvbiBpcyBwcm9wZXJseSBpbnN0YWxsZWQgYW5kIGNvbmZpZ3VyZWRcbiAqXG4gKiAjIyBVc2FnZSBFeGFtcGxlXG4gKlxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGRhdGFiYXNlIGZpcnN0XG4gKiBjb25zdCBkYXRhYmFzZSA9IG5ldyBQZ1N0YWNEYXRhYmFzZSh0aGlzLCAnRGF0YWJhc2UnLCB7XG4gKiAgIHBnc3RhY1ZlcnNpb246ICcwLjkuNSdcbiAqIH0pO1xuICpcbiAqIC8vIENyZWF0ZSBPYmplY3QgbG9hZGVyXG4gKiBjb25zdCBsb2FkZXIgPSBuZXcgU3RhY0xvYWRlcih0aGlzLCAnU3RhY0xvYWRlcicsIHtcbiAqICAgcGdzdGFjRGI6IGRhdGFiYXNlLFxuICogICBiYXRjaFNpemU6IDEwMDAsICAgICAgICAgIC8vIFByb2Nlc3MgdXAgdG8gMTAwMCBvYmplY3RzIHBlciBiYXRjaFxuICogICBtYXhCYXRjaGluZ1dpbmRvd01pbnV0ZXM6IDEsIC8vIFdhaXQgbWF4IDEgbWludXRlIHRvIGZpbGwgYmF0Y2hcbiAqICAgbGFtYmRhVGltZW91dFNlY29uZHM6IDMwMCAgICAgLy8gQWxsb3cgdXAgdG8gMzAwIHNlY29uZHMgZm9yIGRhdGFiYXNlIG9wZXJhdGlvbnNcbiAqIH0pO1xuICpcbiAqIC8vIFRoZSB0b3BpYyBBUk4gY2FuIGJlIHVzZWQgYnkgb3RoZXIgc2VydmljZXMgdG8gcHVibGlzaCBvYmplY3RzXG4gKiBuZXcgQ2ZuT3V0cHV0KHRoaXMsICdMb2FkZXJUb3BpY0FybicsIHtcbiAqICAgdmFsdWU6IGxvYWRlci50b3BpYy50b3BpY0FyblxuICogfSk7XG4gKiBgYGBcbiAqXG4gKiAjIyBEaXJlY3QgT2JqZWN0IFB1Ymxpc2hpbmdcbiAqXG4gKiBFeHRlcm5hbCBzZXJ2aWNlcyBjYW4gcHVibGlzaCBTVEFDIG9iamVjdHMgZGlyZWN0bHkgdG8gdGhlIHRvcGljOlxuICpcbiAqIGBgYGJhc2hcbiAqIGF3cyBzbnMgcHVibGlzaCAtLXRvcGljLWFybiAkU1RBQ19MT0FEX1RPUElDIC0tbWVzc2FnZSAgJ3tcbiAqICAgXCJpZFwiOiBcImV4YW1wbGUtY29sbGVjdGlvblwiLFxuICogICBcInR5cGVcIjogXCJDb2xsZWN0aW9uXCIsXG4gKiAgIFwidGl0bGVcIjogXCJFeGFtcGxlIENvbGxlY3Rpb25cIixcbiAqICAgXCJkZXNjcmlwdGlvblwiOiBcIkFuIGV4YW1wbGUgY29sbGVjdGlvblwiLFxuICogICBcImxpY2Vuc2VcIjogXCJwcm9wcmlldGFyeVwiLFxuICogICBcImV4dGVudFwiOiB7XG4gKiAgICAgICBcInNwYXRpYWxcIjoge1wiYmJveFwiOiBbWy0xODAsIC05MCwgMTgwLCA5MF1dfSxcbiAqICAgICAgIFwidGVtcG9yYWxcIjoge1wiaW50ZXJ2YWxcIjogW1tudWxsLCBudWxsXV19LFxuICogICB9LFxuICogICBcInN0YWNfdmVyc2lvblwiOiBcIjEuMS4wXCIsXG4gKiB9J1xuICpcbiAqIGF3cyBzbnMgcHVibGlzaCAtLXRvcGljLWFybiAkU1RBQ19MT0FEX1RPUElDIC0tbWVzc2FnZSAne1xuICogICBcInR5cGVcIjogXCJGZWF0dXJlXCIsXG4gKiAgIFwic3RhY192ZXJzaW9uXCI6IFwiMS4wLjBcIixcbiAqICAgXCJpZFwiOiBcImV4YW1wbGUtaXRlbVwiLFxuICogICBcInByb3BlcnRpZXNcIjoge1wiZGF0ZXRpbWVcIjogXCIyMDIxLTAxLTAxVDAwOjAwOjAwWlwifSxcbiAqICAgXCJnZW9tZXRyeVwiOiB7XCJ0eXBlXCI6IFwiUG9seWdvblwiLCBcImNvb3JkaW5hdGVzXCI6IFsuLi5dfSxcbiAqICAgXCJjb2xsZWN0aW9uXCI6IFwiZXhhbXBsZS1jb2xsZWN0aW9uXCJcbiAqIH0nXG4gKlxuICpcbiAqIGBgYFxuICpcbiAqICMjIFMzIEV2ZW50IENvbmZpZ3VyYXRpb25cbiAqXG4gKiBUbyBlbmFibGUgUzMgZXZlbnQtZHJpdmVuIGxvYWRpbmcsIGNvbmZpZ3VyZSBTMyBidWNrZXQgbm90aWZpY2F0aW9ucyB0byBzZW5kXG4gKiBldmVudHMgdG8gdGhlIFNOUyB0b3BpYyB3aGVuIFNUQUMgb2JqZWN0cyAoLmpzb24gb3IgLmdlb2pzb24gZmlsZXMpIGFyZSB1cGxvYWRlZDpcbiAqXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDb25maWd1cmUgUzMgYnVja2V0IHRvIHNlbmQgbm90aWZpY2F0aW9ucyB0byB0aGUgbG9hZGVyIHRvcGljXG4gKiBidWNrZXQuYWRkRXZlbnROb3RpZmljYXRpb24oXG4gKiAgIHMzLkV2ZW50VHlwZS5PQkpFQ1RfQ1JFQVRFRCxcbiAqICAgbmV3IHMzbi5TbnNEZXN0aW5hdGlvbihsb2FkZXIudG9waWMpLFxuICogICB7IHN1ZmZpeDogJy5qc29uJyB9XG4gKiApO1xuICpcbiAqIGJ1Y2tldC5hZGRFdmVudE5vdGlmaWNhdGlvbihcbiAqICAgczMuRXZlbnRUeXBlLk9CSkVDVF9DUkVBVEVELFxuICogICBuZXcgczNuLlNuc0Rlc3RpbmF0aW9uKGxvYWRlci50b3BpYyksXG4gKiAgIHsgc3VmZml4OiAnLmdlb2pzb24nIH1cbiAqICk7XG4gKiBgYGBcbiAqXG4gKiBXaGVuIFNUQUMgb2JqZWN0cyBhcmUgdXBsb2FkZWQgdG8gdGhlIGNvbmZpZ3VyZWQgUzMgYnVja2V0LCB0aGUgbG9hZGVyIHdpbGw6XG4gKiAxLiBSZWNlaXZlIFMzIGV2ZW50IG5vdGlmaWNhdGlvbnMgdmlhIFNOU1xuICogMi4gRmV0Y2ggdGhlIFNUQUMgSlNPTiBmcm9tIFMzXG4gKiAzLiBWYWxpZGF0ZSBhbmQgbG9hZCB0aGUgb2JqZWN0cyBpbnRvIHRoZSBwZ3N0YWMgZGF0YWJhc2VcbiAqXG4gKiAjIyBNb25pdG9yaW5nIGFuZCBUcm91Ymxlc2hvb3RpbmdcbiAqXG4gKiAtIE1vbml0b3IgTGFtYmRhIGxvZ3M6IGAvYXdzL2xhbWJkYS97RnVuY3Rpb25OYW1lfWBcbiAqIC0gKipEZWFkIExldHRlciBRdWV1ZSoqOiBDaGVjayBmb3IgZmFpbGVkIG9iamVjdHMgLSAqKm5vIGF1dG9tYXRlZCBoYW5kbGluZyBwcm92aWRlZCoqXG4gKiAtIFVzZSBiYXRjaCBvYmplY3RzIGZhaWx1cmUgcmVwb3J0aW5nIGZvciBwYXJ0aWFsIGJhdGNoIHByb2Nlc3NpbmdcbiAqIC0gQ2xvdWRXYXRjaCBtZXRyaWNzIGF2YWlsYWJsZSBmb3IgcXVldWUgZGVwdGggYW5kIExhbWJkYSBwZXJmb3JtYW5jZVxuICpcbiAqICMjIyBEZWFkIExldHRlciBRdWV1ZSBNYW5hZ2VtZW50XG4gKlxuICogQXBwbGljYXRpb25zIG11c3QgaW1wbGVtZW50IHRoZWlyIG93biBkZWFkIGxldHRlciBxdWV1ZSBtb25pdG9yaW5nOlxuICpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEV4YW1wbGU6IENsb3VkV2F0Y2ggYWxhcm0gZm9yIGRlYWQgbGV0dGVyIHF1ZXVlIGRlcHRoXG4gKiBuZXcgY2xvdWR3YXRjaC5BbGFybSh0aGlzLCAnRGVhZExldHRlckFsYXJtJywge1xuICogICBtZXRyaWM6IGxvYWRlci5kZWFkTGV0dGVyUXVldWUubWV0cmljQXBwcm94aW1hdGVOdW1iZXJPZlZpc2libGVNZXNzYWdlcygpLFxuICogICB0aHJlc2hvbGQ6IDEsXG4gKiAgIGV2YWx1YXRpb25QZXJpb2RzOiAxXG4gKiB9KTtcbiAqXG4gKiAvLyBFeGFtcGxlOiBMYW1iZGEgdG8gcmVwcm9jZXNzIGRlYWQgbGV0dGVyIG1lc3NhZ2VzXG4gKiBjb25zdCByZXByb2Nlc3NGdW5jdGlvbiA9IG5ldyBsYW1iZGEuRnVuY3Rpb24odGhpcywgJ1JlcHJvY2VzcycsIHtcbiAqICAgLy8gSW1wbGVtZW50YXRpb24gdG8gZmV0Y2ggYW5kIHJlcHVibGlzaCBmYWlsZWQgbWVzc2FnZXNcbiAqIH0pO1xuICogYGBgXG4gKlxuICovXG5leHBvcnQgY2xhc3MgU3RhY0xvYWRlciBleHRlbmRzIENvbnN0cnVjdCB7XG4gIC8qKlxuICAgKiBUaGUgU05TIHRvcGljIHRoYXQgcmVjZWl2ZXMgU1RBQyBvYmplY3RzIGFuZCBTMyBldmVudCBub3RpZmljYXRpb25zIGZvciBsb2FkaW5nLlxuICAgKlxuICAgKiBUaGlzIHRvcGljIHNlcnZlcyBhcyB0aGUgZW50cnkgcG9pbnQgZm9yIHR3byB0eXBlcyBvZiBldmVudHM6XG4gICAqIDEuIERpcmVjdCBTVEFDIEpTT04gZG9jdW1lbnRzIHB1Ymxpc2hlZCBieSBleHRlcm5hbCBzZXJ2aWNlc1xuICAgKiAyLiBTMyBldmVudCBub3RpZmljYXRpb25zIHdoZW4gU1RBQyBvYmplY3RzIGFyZSB1cGxvYWRlZCB0byBjb25maWd1cmVkIGJ1Y2tldHNcbiAgICpcbiAgICogVGhlIHRvcGljIGZhbnMgb3V0IHRvIHRoZSBTUVMgcXVldWUgZm9yIGJhdGNoZWQgcHJvY2Vzc2luZy5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSB0b3BpYzogc25zLlRvcGljO1xuXG4gIC8qKlxuICAgKiBUaGUgU1FTIHF1ZXVlIHRoYXQgYnVmZmVycyBtZXNzYWdlcyBiZWZvcmUgcHJvY2Vzc2luZy5cbiAgICpcbiAgICogVGhpcyBxdWV1ZSBjb2xsZWN0cyBib3RoIGRpcmVjdCBTVEFDIG9iamVjdHMgZnJvbSBTTlMgYW5kIFMzIGV2ZW50XG4gICAqIG5vdGlmaWNhdGlvbnMsIGJhdGNoaW5nIHRoZW0gZm9yIGVmZmljaWVudCBkYXRhYmFzZSBvcGVyYXRpb25zLlxuICAgKiBDb25maWd1cmVkIHdpdGggYSB2aXNpYmlsaXR5IHRpbWVvdXQgdGhhdCBhY2NvbW1vZGF0ZXMgTGFtYmRhXG4gICAqIHByb2Nlc3NpbmcgdGltZSBwbHVzIGJ1ZmZlci5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSBxdWV1ZTogc3FzLlF1ZXVlO1xuXG4gIC8qKlxuICAgKiBEZWFkIGxldHRlciBxdWV1ZSBmb3IgZmFpbGVkIG9iamVjdHMgbG9hZGluZyBhdHRlbXB0cy5cbiAgICpcbiAgICogTWVzc2FnZXMgdGhhdCBmYWlsIHByb2Nlc3NpbmcgYWZ0ZXIgNSBhdHRlbXB0cyBhcmUgc2VudCBoZXJlXG4gICAqIGZvciBpbnNwZWN0aW9uIGFuZCBwb3RlbnRpYWwgcmVwbGF5LiBSZXRhaW5zIG1lc3NhZ2VzIGZvciAxNCBkYXlzXG4gICAqIHRvIGFsbG93IGZvciBkZWJ1Z2dpbmcgYW5kIG1hbnVhbCBpbnRlcnZlbnRpb24uXG4gICAqXG4gICAqICoqVXNlciBSZXNwb25zaWJpbGl0eSoqOiBUaGlzIGNvbnN0cnVjdCBwcm92aWRlcyBOTyBhdXRvbWF0ZWQgbW9uaXRvcmluZyxcbiAgICogYWxlcnRpbmcsIG9yIHJlcHJvY2Vzc2luZyBvZiBkZWFkIGxldHRlciBxdWV1ZSBtZXNzYWdlcy4gQXBwbGljYXRpb25zXG4gICAqIHVzaW5nIHRoaXMgY29uc3RydWN0IG11c3QgaW1wbGVtZW50IHRoZWlyIG93bjpcbiAgICogLSBEZWFkIGxldHRlciBxdWV1ZSBkZXB0aCBtb25pdG9yaW5nIGFuZCBhbGVydGluZ1xuICAgKiAtIEZhaWxlZCBtZXNzYWdlIGluc3BlY3Rpb24gYW5kIGRlYnVnZ2luZyB3b3JrZmxvd3NcbiAgICogLSBNYW51YWwgb3IgYXV0b21hdGVkIHJlcHJvY2Vzc2luZyBtZWNoYW5pc21zXG4gICAqIC0gQ2xlYW51cCBwcm9jZWR1cmVzIGZvciBvbGQgZmFpbGVkIG1lc3NhZ2VzXG4gICAqL1xuICBwdWJsaWMgcmVhZG9ubHkgZGVhZExldHRlclF1ZXVlOiBzcXMuUXVldWU7XG5cbiAgLyoqXG4gICAqIFRoZSBMYW1iZGEgZnVuY3Rpb24gdGhhdCBsb2FkcyBTVEFDIG9iamVjdHMgaW50byB0aGUgcGdzdGFjIGRhdGFiYXNlLlxuICAgKlxuICAgKiBUaGlzIFB5dGhvbiBmdW5jdGlvbiByZWNlaXZlcyBiYXRjaGVzIG9mIG1lc3NhZ2VzIGZyb20gU1FTIGFuZCBwcm9jZXNzZXNcbiAgICogdGhlbSBiYXNlZCBvbiB0aGVpciB0eXBlOlxuICAgKiAtIERpcmVjdCBTVEFDIG9iamVjdHM6IFZhbGlkYXRlcyBhbmQgbG9hZHMgZGlyZWN0bHkgaW50byBwZ3N0YWNcbiAgICogLSBTMyBldmVudHM6IEZldGNoZXMgU1RBQyBKU09OIGZyb20gUzMsIHZhbGlkYXRlcywgYW5kIGxvYWRzIGludG8gcGdzdGFjXG4gICAqXG4gICAqIFRoZSBmdW5jdGlvbiBjb25uZWN0cyB0byBQb3N0Z3JlU1FMIHVzaW5nIGNyZWRlbnRpYWxzIGZyb20gU2VjcmV0cyBNYW5hZ2VyXG4gICAqIGFuZCB1c2VzIHB5cGdzdGFjIGZvciBlZmZpY2llbnQgZGF0YWJhc2Ugb3BlcmF0aW9ucy5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSBsYW1iZGFGdW5jdGlvbjogbGFtYmRhLkZ1bmN0aW9uO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBTdGFjTG9hZGVyUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgdGltZW91dFNlY29uZHMgPSBwcm9wcy5sYW1iZGFUaW1lb3V0U2Vjb25kcyA/PyAzMDA7XG4gICAgY29uc3QgbGFtYmRhUnVudGltZSA9IHByb3BzLmxhbWJkYVJ1bnRpbWUgPz8gbGFtYmRhLlJ1bnRpbWUuUFlUSE9OXzNfMTE7XG4gICAgY29uc3QgbWF4Q29uY3VycmVuY3kgPSBwcm9wcy5tYXhDb25jdXJyZW5jeSA/PyAyO1xuXG4gICAgLy8gQ3JlYXRlIGRlYWQgbGV0dGVyIHF1ZXVlXG4gICAgdGhpcy5kZWFkTGV0dGVyUXVldWUgPSBuZXcgc3FzLlF1ZXVlKHRoaXMsIFwiRGVhZExldHRlclF1ZXVlXCIsIHtcbiAgICAgIHJldGVudGlvblBlcmlvZDogRHVyYXRpb24uZGF5cygxNCksXG4gICAgfSk7XG5cbiAgICAvLyBDcmVhdGUgbWFpbiBxdWV1ZVxuICAgIHRoaXMucXVldWUgPSBuZXcgc3FzLlF1ZXVlKHRoaXMsIFwiUXVldWVcIiwge1xuICAgICAgdmlzaWJpbGl0eVRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHModGltZW91dFNlY29uZHMgKyAxMCksXG4gICAgICBlbmNyeXB0aW9uOiBzcXMuUXVldWVFbmNyeXB0aW9uLlNRU19NQU5BR0VELFxuICAgICAgZGVhZExldHRlclF1ZXVlOiB7XG4gICAgICAgIG1heFJlY2VpdmVDb3VudDogNSxcbiAgICAgICAgcXVldWU6IHRoaXMuZGVhZExldHRlclF1ZXVlLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBTTlMgdG9waWNcbiAgICB0aGlzLnRvcGljID0gbmV3IHNucy5Ub3BpYyh0aGlzLCBcIlRvcGljXCIsIHtcbiAgICAgIGRpc3BsYXlOYW1lOiBgJHtpZH0tU3RhY0xvYWRlclRvcGljYCxcbiAgICB9KTtcblxuICAgIC8vIFN1YnNjcmliZSB0aGUgcXVldWUgdG8gdGhlIHRvcGljXG4gICAgdGhpcy50b3BpYy5hZGRTdWJzY3JpcHRpb24oXG4gICAgICBuZXcgc25zU3Vic2NyaXB0aW9ucy5TcXNTdWJzY3JpcHRpb24odGhpcy5xdWV1ZSlcbiAgICApO1xuXG4gICAgLy8gQ3JlYXRlIHRoZSBsYW1iZGEgZnVuY3Rpb25cbiAgICB0aGlzLmxhbWJkYUZ1bmN0aW9uID0gbmV3IGxhbWJkYS5GdW5jdGlvbih0aGlzLCBcIkZ1bmN0aW9uXCIsIHtcbiAgICAgIHJ1bnRpbWU6IGxhbWJkYVJ1bnRpbWUsXG4gICAgICBoYW5kbGVyOiBcInN0YWNfbG9hZGVyLmhhbmRsZXIuaGFuZGxlclwiLFxuICAgICAgdnBjOiBwcm9wcy52cGMsXG4gICAgICB2cGNTdWJuZXRzOiBwcm9wcy5zdWJuZXRTZWxlY3Rpb24sXG4gICAgICBjb2RlOiBsYW1iZGEuQ29kZS5mcm9tRG9ja2VyQnVpbGQocGF0aC5qb2luKF9fZGlybmFtZSwgXCIuLlwiKSwge1xuICAgICAgICBmaWxlOiBcInN0YWMtbG9hZGVyL3J1bnRpbWUvRG9ja2VyZmlsZVwiLFxuICAgICAgICBwbGF0Zm9ybTogXCJsaW51eC9hbWQ2NFwiLFxuICAgICAgICBidWlsZEFyZ3M6IHtcbiAgICAgICAgICBQWVRIT05fVkVSU0lPTjogbGFtYmRhUnVudGltZS50b1N0cmluZygpLnJlcGxhY2UoXCJweXRob25cIiwgXCJcIiksXG4gICAgICAgICAgUEdTVEFDX1ZFUlNJT046IHByb3BzLnBnc3RhY0RiLnBnc3RhY1ZlcnNpb24sXG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgICAgIG1lbW9yeVNpemU6IHByb3BzLm1lbW9yeVNpemUgPz8gMTAyNCxcbiAgICAgIHRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHModGltZW91dFNlY29uZHMpLFxuICAgICAgcmVzZXJ2ZWRDb25jdXJyZW50RXhlY3V0aW9uczogbWF4Q29uY3VycmVuY3ksXG4gICAgICBsb2dSZXRlbnRpb246IGxvZ3MuUmV0ZW50aW9uRGF5cy5PTkVfV0VFSyxcbiAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgIFBHU1RBQ19TRUNSRVRfQVJOOiBwcm9wcy5wZ3N0YWNEYi5wZ3N0YWNTZWNyZXQuc2VjcmV0QXJuLFxuICAgICAgICAuLi5wcm9wcy5lbnZpcm9ubWVudCxcbiAgICAgIH0sXG4gICAgICAvLyBvdmVyd3JpdGVzIGRlZmF1bHRzIHdpdGggdXNlci1wcm92aWRlZCBjb25maWd1cmFibGUgcHJvcGVydGllc1xuICAgICAgLi4ucHJvcHMubGFtYmRhRnVuY3Rpb25PcHRpb25zLFxuICAgIH0pO1xuXG4gICAgLy8gR3JhbnQgcGVybWlzc2lvbnMgdG8gcmVhZCB0aGUgZGF0YWJhc2Ugc2VjcmV0XG4gICAgcHJvcHMucGdzdGFjRGIucGdzdGFjU2VjcmV0LmdyYW50UmVhZCh0aGlzLmxhbWJkYUZ1bmN0aW9uKTtcblxuICAgIC8vIEFkZCBTUVMgZXZlbnQgc291cmNlIHRvIHRoZSBsYW1iZGFcbiAgICB0aGlzLmxhbWJkYUZ1bmN0aW9uLmFkZEV2ZW50U291cmNlKFxuICAgICAgbmV3IGxhbWJkYUV2ZW50U291cmNlcy5TcXNFdmVudFNvdXJjZSh0aGlzLnF1ZXVlLCB7XG4gICAgICAgIGJhdGNoU2l6ZTogcHJvcHMuYmF0Y2hTaXplID8/IDUwMCxcbiAgICAgICAgbWF4QmF0Y2hpbmdXaW5kb3c6IER1cmF0aW9uLm1pbnV0ZXMoXG4gICAgICAgICAgcHJvcHMubWF4QmF0Y2hpbmdXaW5kb3dNaW51dGVzID8/IDFcbiAgICAgICAgKSxcbiAgICAgICAgbWF4Q29uY3VycmVuY3k6IG1heENvbmN1cnJlbmN5LFxuICAgICAgICByZXBvcnRCYXRjaEl0ZW1GYWlsdXJlczogdHJ1ZSxcbiAgICAgIH0pXG4gICAgKTtcblxuICAgIC8vIENyZWF0ZSBvdXRwdXRzXG4gICAgY29uc3QgZXhwb3J0UHJlZml4ID0gU3RhY2sub2YodGhpcykuc3RhY2tOYW1lO1xuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJUb3BpY0FyblwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy50b3BpYy50b3BpY0FybixcbiAgICAgIGRlc2NyaXB0aW9uOiBcIkFSTiBvZiB0aGUgU3RhY0xvYWRlciBTTlMgVG9waWNcIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItdG9waWMtYXJuYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJRdWV1ZVVybFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5xdWV1ZS5xdWV1ZVVybCxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIlVSTCBvZiB0aGUgU3RhY0xvYWRlciBTUVMgUXVldWVcIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItcXVldWUtdXJsYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJEZWFkTGV0dGVyUXVldWVVcmxcIiwge1xuICAgICAgdmFsdWU6IHRoaXMuZGVhZExldHRlclF1ZXVlLnF1ZXVlVXJsLFxuICAgICAgZGVzY3JpcHRpb246IFwiVVJMIG9mIHRoZSBTdGFjTG9hZGVyIERlYWQgTGV0dGVyIFF1ZXVlXCIsXG4gICAgICBleHBvcnROYW1lOiBgJHtleHBvcnRQcmVmaXh9LXN0YWMtbG9hZGVyLWRlYWRsZXR0ZXItcXVldWUtdXJsYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJGdW5jdGlvbk5hbWVcIiwge1xuICAgICAgdmFsdWU6IHRoaXMubGFtYmRhRnVuY3Rpb24uZnVuY3Rpb25OYW1lLFxuICAgICAgZGVzY3JpcHRpb246IFwiTmFtZSBvZiB0aGUgU3RhY0xvYWRlciBMYW1iZGEgRnVuY3Rpb25cIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItZnVuY3Rpb24tbmFtZWAsXG4gICAgfSk7XG4gIH1cbn1cblxuLyoqXG4gKiBAZGVwcmVjYXRlZCBVc2UgU3RhY0xvYWRlciBpbnN0ZWFkLiBTdGFjSXRlbUxvYWRlciB3aWxsIGJlIHJlbW92ZWQgaW4gYSBmdXR1cmUgdmVyc2lvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIFN0YWNJdGVtTG9hZGVyIGV4dGVuZHMgU3RhY0xvYWRlciB7XG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBTdGFjTG9hZGVyUHJvcHMpIHtcbiAgICBjb25zb2xlLndhcm4oXG4gICAgICBgU3RhY0l0ZW1Mb2FkZXIgaXMgZGVwcmVjYXRlZC4gUGxlYXNlIHVzZSBTdGFjTG9hZGVyIGluc3RlYWQuIGAgK1xuICAgICAgICBgU3RhY0l0ZW1Mb2FkZXIgd2lsbCBiZSByZW1vdmVkIGluIGEgZnV0dXJlIHZlcnNpb24uYFxuICAgICk7XG5cbiAgICBzdXBlcihzY29wZSwgaWQsIHByb3BzKTtcbiAgfVxufVxuXG4vLyBBbHNvIGNyZWF0ZSBhIGRlcHJlY2F0ZWQgaW50ZXJmYWNlIGFsaWFzIGlmIHlvdSBoYWQgYSBzZXBhcmF0ZSBpbnRlcmZhY2Vcbi8qKlxuICogQGRlcHJlY2F0ZWQgVXNlIFN0YWNMb2FkZXJQcm9wcyBpbnN0ZWFkLiBTdGFjSXRlbUxvYWRlclByb3BzIHdpbGwgYmUgcmVtb3ZlZCBpbiBhIGZ1dHVyZSB2ZXJzaW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YWNJdGVtTG9hZGVyUHJvcHMgZXh0ZW5kcyBTdGFjTG9hZGVyUHJvcHMge31cbiJdfQ==
|
|
@@ -7,12 +7,12 @@ ENV PYTHONUNBUFFERED=1
|
|
|
7
7
|
|
|
8
8
|
WORKDIR /asset
|
|
9
9
|
|
|
10
|
-
COPY stac-
|
|
11
|
-
COPY stac-
|
|
10
|
+
COPY stac-loader/runtime/pyproject.toml pyproject.toml
|
|
11
|
+
COPY stac-loader/runtime/src/stac_loader/ stac_loader/
|
|
12
12
|
|
|
13
13
|
ARG PGSTAC_VERSION=0.9.6
|
|
14
14
|
RUN uv add --no-sync pypgstac==${PGSTAC_VERSION} && \
|
|
15
15
|
uv export --no-dev --no-editable -o requirements.txt && \
|
|
16
16
|
uv pip install --target /asset -r requirements.txt
|
|
17
17
|
|
|
18
|
-
CMD ["
|
|
18
|
+
CMD ["stac_loader.handler.handler"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
|
-
name = "stac-
|
|
2
|
+
name = "stac-loader"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
description = "An application for loading STAC items into a pgstac database"
|
|
4
|
+
description = "An application for loading STAC collections and items into a pgstac database"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "hrodmn", email = "henry@developmentseed.org" }
|
|
7
7
|
]
|
|
@@ -92,40 +92,40 @@ def is_s3_event(message_str: str) -> bool:
|
|
|
92
92
|
return "aws:s3" in message_str
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def
|
|
96
|
-
"""Fetch STAC
|
|
95
|
+
def get_stac_object_from_s3(bucket_name: str, object_key: str) -> Dict[str, Any]:
|
|
96
|
+
"""Fetch STAC JSON from S3."""
|
|
97
97
|
session = boto3.session.Session()
|
|
98
98
|
s3_client = session.client("s3")
|
|
99
99
|
|
|
100
100
|
try:
|
|
101
|
-
logger.debug(f"Fetching STAC
|
|
101
|
+
logger.debug(f"Fetching STAC object from s3://{bucket_name}/{object_key}")
|
|
102
102
|
response = s3_client.get_object(Bucket=bucket_name, Key=object_key)
|
|
103
103
|
content = response["Body"].read()
|
|
104
104
|
|
|
105
105
|
try:
|
|
106
|
-
|
|
106
|
+
stac_json = content.decode("utf-8")
|
|
107
107
|
except UnicodeDecodeError as e:
|
|
108
108
|
logger.error(
|
|
109
109
|
f"Failed to decode S3 object as UTF-8: s3://{bucket_name}/{object_key}"
|
|
110
110
|
)
|
|
111
111
|
raise ValueError("S3 object is not valid UTF-8 text") from e
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
stac_data = json.loads(stac_json)
|
|
114
114
|
logger.debug(
|
|
115
|
-
f"Successfully parsed STAC
|
|
115
|
+
f"Successfully parsed STAC metadata from S3: {stac_data.get('id', 'unknown')}"
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
-
return
|
|
118
|
+
return stac_data
|
|
119
119
|
|
|
120
120
|
except Exception as e:
|
|
121
121
|
logger.error(
|
|
122
|
-
f"Failed to fetch STAC
|
|
122
|
+
f"Failed to fetch STAC metadata from s3://{bucket_name}/{object_key}: {e}"
|
|
123
123
|
)
|
|
124
124
|
raise
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def process_s3_event(message_str: str) -> Dict[str, Any]:
|
|
128
|
-
"""Process an S3 event notification and return STAC
|
|
128
|
+
"""Process an S3 event notification and return STAC metadata."""
|
|
129
129
|
try:
|
|
130
130
|
message_data = json.loads(message_str)
|
|
131
131
|
records: List[Dict[str, Any]] = message_data.get("Records", [])
|
|
@@ -138,15 +138,15 @@ def process_s3_event(message_str: str) -> Dict[str, Any]:
|
|
|
138
138
|
bucket_name = s3_data["bucket"]["name"]
|
|
139
139
|
object_key = s3_data["object"]["key"]
|
|
140
140
|
|
|
141
|
-
# Validate that this looks like a STAC
|
|
141
|
+
# Validate that this looks like a STAC file
|
|
142
142
|
if not object_key.endswith((".json", ".geojson")):
|
|
143
143
|
raise ValueError(
|
|
144
|
-
f"S3 object key does not appear to be a STAC
|
|
144
|
+
f"S3 object key does not appear to be a STAC document: {object_key}"
|
|
145
145
|
)
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
stac_data = get_stac_object_from_s3(bucket_name, object_key)
|
|
148
148
|
|
|
149
|
-
return
|
|
149
|
+
return stac_data
|
|
150
150
|
|
|
151
151
|
except KeyError as e:
|
|
152
152
|
logger.error(f"S3 event missing required field: {e}")
|
|
@@ -169,8 +169,10 @@ def handler(
|
|
|
169
169
|
)
|
|
170
170
|
pgstac_dsn = get_pgstac_dsn()
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
batch_failures: List[BatchItemFailure] = []
|
|
173
173
|
|
|
174
|
+
collections: List[Dict[str, Any]] = []
|
|
175
|
+
collection_message_ids: List[str] = []
|
|
174
176
|
items_by_collection: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
175
177
|
message_ids_by_collection: DefaultDict[str, List[str]] = defaultdict(list)
|
|
176
178
|
|
|
@@ -194,21 +196,47 @@ def handler(
|
|
|
194
196
|
else:
|
|
195
197
|
message_data = json.loads(message_str)
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
if message_data["type"] == "Feature":
|
|
200
|
+
item = Item(**message_data)
|
|
198
201
|
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
if not item.collection:
|
|
203
|
+
raise KeyError(f"item {item.id} is missing a collection id!")
|
|
204
|
+
|
|
205
|
+
items_by_collection[item.collection].append(item.model_dump(mode="json"))
|
|
206
|
+
message_ids_by_collection[item.collection].append(message_id)
|
|
207
|
+
elif message_data["type"] == "Collection":
|
|
208
|
+
collection = Collection(**message_data)
|
|
209
|
+
collections.append(collection.model_dump(mode="json"))
|
|
210
|
+
collection_message_ids.append(message_id)
|
|
211
|
+
else:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"expected either a 'Feature' or a 'Collection', received a {message_data['type']}"
|
|
214
|
+
)
|
|
201
215
|
|
|
202
|
-
items_by_collection[item.collection].append(item.model_dump(mode="json"))
|
|
203
|
-
message_ids_by_collection[item.collection].append(message_id)
|
|
204
216
|
logger.debug(f"[{message_id}] Successfully processed.")
|
|
205
217
|
|
|
206
218
|
except (ValueError, KeyError, ValidationError, json.JSONDecodeError) as e:
|
|
207
219
|
logger.error(f"[{message_id}] Failed with error: {e}", extra=record)
|
|
208
|
-
|
|
220
|
+
batch_failures.append({"itemIdentifier": message_id})
|
|
209
221
|
except Exception as e:
|
|
210
222
|
logger.error(f"[{message_id}] Unexpected error: {e}", extra=record)
|
|
211
|
-
|
|
223
|
+
batch_failures.append({"itemIdentifier": message_id})
|
|
224
|
+
|
|
225
|
+
if collections:
|
|
226
|
+
try:
|
|
227
|
+
with PgstacDB(dsn=pgstac_dsn) as db:
|
|
228
|
+
loader = Loader(db=db)
|
|
229
|
+
logger.info("loading collections into database.")
|
|
230
|
+
loader.load_collections(
|
|
231
|
+
file=collections, # type: ignore
|
|
232
|
+
insert_mode=Methods.upsert,
|
|
233
|
+
)
|
|
234
|
+
logger.info(f"successfully loaded {len(collections)} collections.")
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"failed to load collections: {str(e)}")
|
|
237
|
+
batch_failures.extend(
|
|
238
|
+
[{"itemIdentifier": message_id} for message_id in collection_message_ids]
|
|
239
|
+
)
|
|
212
240
|
|
|
213
241
|
for collection_id, items in items_by_collection.items():
|
|
214
242
|
try:
|
|
@@ -248,21 +276,21 @@ def handler(
|
|
|
248
276
|
except Exception as e:
|
|
249
277
|
logger.error(f"[{collection_id}] failed to load items: {str(e)}")
|
|
250
278
|
|
|
251
|
-
|
|
279
|
+
batch_failures.extend(
|
|
252
280
|
[
|
|
253
281
|
{"itemIdentifier": message_id}
|
|
254
282
|
for message_id in message_ids_by_collection[collection_id]
|
|
255
283
|
]
|
|
256
284
|
)
|
|
257
285
|
|
|
258
|
-
if
|
|
286
|
+
if batch_failures:
|
|
259
287
|
logger.warning(
|
|
260
|
-
f"Finished processing batch. {len(
|
|
288
|
+
f"Finished processing batch. {len(batch_failures)} failure(s) reported."
|
|
261
289
|
)
|
|
262
290
|
logger.info(
|
|
263
|
-
f"Returning failed item identifiers: {[f['itemIdentifier'] for f in
|
|
291
|
+
f"Returning failed item identifiers: {[f['itemIdentifier'] for f in batch_failures]}"
|
|
264
292
|
)
|
|
265
|
-
return {"batchItemFailures":
|
|
293
|
+
return {"batchItemFailures": batch_failures}
|
|
266
294
|
else:
|
|
267
295
|
logger.info("Finished processing batch. All records successful.")
|
|
268
296
|
return None
|
|
@@ -93,7 +93,7 @@ export interface StactoolsItemGeneratorProps {
|
|
|
93
93
|
/**
|
|
94
94
|
* ARN of the SNS topic to publish generated items to.
|
|
95
95
|
*
|
|
96
|
-
* This is typically the topic from a
|
|
96
|
+
* This is typically the topic from a StacLoader construct.
|
|
97
97
|
* Generated STAC items will be published here for downstream
|
|
98
98
|
* processing and database insertion.
|
|
99
99
|
*/
|
|
@@ -158,7 +158,7 @@ export interface StactoolsItemGeneratorProps {
|
|
|
158
158
|
*
|
|
159
159
|
* ```typescript
|
|
160
160
|
* // Create item loader first (or get existing topic ARN)
|
|
161
|
-
* const loader = new
|
|
161
|
+
* const loader = new StacLoader(this, 'ItemLoader', {
|
|
162
162
|
* pgstacDb: database
|
|
163
163
|
* });
|
|
164
164
|
*
|