Skip to content

Commit

Permalink
Merge branch 'master' into v1.17.0
Browse files Browse the repository at this point in the history
# Conflicts:
#	dynamodb-documents/src/main/java/com/formkiq/stacks/dynamodb/DocumentServiceImpl.java
  • Loading branch information
mfriesen committed Jan 24, 2025
2 parents ccd65e3 + 959e655 commit ca2d09c
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
echo "$HOME/.local/bin" >> $GITHUB_PATH
ytt --version
- name: Execute Gradle build
run: ./gradlew build --refresh-dependencies --info
run: ./gradlew clean build --refresh-dependencies --info
- name: Upload test reports
uses: actions/upload-artifact@v4
if: always()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* MIT License
*
* Copyright (c) 2018 - 2020 FormKiQ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.formkiq.aws.dynamodb;

import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* {@link Predicate} for finding matching {@link DynamodbRecord} keys.
*/
public class DynamodbRecordKeyPredicate implements Predicate<DynamodbRecord<?>> {

/** {@link Set} Keys. */
private final Set<String> keys;

/**
* constructor.
*
* @param records {@link Collection} {@link DynamodbRecord}
*/
public DynamodbRecordKeyPredicate(final Collection<? extends DynamodbRecord<?>> records) {
this.keys = records.stream().map(DynamodbRecordKeyPredicate::key).collect(Collectors.toSet());
}

@Override
public boolean test(final DynamodbRecord r) {
return keys.contains(key(r));
}

/**
* Generate Key.
*
* @param r {@link DynamodbRecord}
* @return String
*/
public static String key(final DynamodbRecord<?> r) {
return r.pk(null) + "#" + r.sk();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.formkiq.aws.dynamodb.DynamoDbService;
import com.formkiq.aws.dynamodb.DynamoDbServiceImpl;
import com.formkiq.aws.dynamodb.DynamodbRecord;
import com.formkiq.aws.dynamodb.DynamodbRecordKeyPredicate;
import com.formkiq.aws.dynamodb.DynamodbRecordTx;
import com.formkiq.aws.dynamodb.PaginationMapToken;
import com.formkiq.aws.dynamodb.PaginationResult;
Expand Down Expand Up @@ -321,14 +322,11 @@ private Collection<DocumentAttributeRecord> generateDocumentAttributesToSave(fin
newDocumentAttributeRecords = concat(newDocumentAttributeRecords, defaultValues);
allAttributes = concat(allAttributes, defaultValues);

Collection<DocumentAttributeRecord> allCompositeKeys =
new SchemaCompositeKeyGenerator().apply(schemaAttributes, documentId, allAttributes);

Collection<DocumentAttributeRecord> newCompositeKeys =
getNewCompositeKeys(allCompositeKeys, previousCompositeKeys);
new SchemaCompositeKeyGenerator().apply(schemaAttributes, documentId, allAttributes);

Collection<DocumentAttributeRecord> compositeKeysToBeDeleted = previousCompositeKeys.stream()
.filter(Predicate.not(new DocumentAttributeKeyPredicate(allCompositeKeys))).toList();
.filter(Predicate.not(new DocumentAttributeKeyPredicate(newCompositeKeys))).toList();

Collection<DocumentAttributeRecord> documentAttributes =
Objects.concat(newDocumentAttributeRecords, newCompositeKeys);
Expand All @@ -343,18 +341,6 @@ private Collection<DocumentAttributeRecord> generateDocumentAttributesToSave(fin
return documentAttributes;
}

private Collection<DocumentAttributeRecord> getNewCompositeKeys(
final Collection<DocumentAttributeRecord> allCompositeKeys,
final Collection<DocumentAttributeRecord> previousCompositeKeys) {

Set<String> keys = createAttributeKeys(previousCompositeKeys);
Set<String> attributeKeys = previousCompositeKeys.stream().map(DocumentAttributeRecord::getKey)
.collect(Collectors.toSet());

return allCompositeKeys.stream().filter(a -> !attributeKeys.contains(a.getKey())
|| isDocumentAttributeKeyMatchPredicate(a, attributeKeys, keys)).toList();
}

/**
* Find all a document's attributes.
*
Expand Down Expand Up @@ -1430,19 +1416,6 @@ private Schema getSchame(final String siteId) {
return this.schemaService.getSitesSchema(siteId);
}

/**
* Is {@link List} {@link DynamicObject} contain a non generated tag.
*
* @param tags {@link List} {@link DynamicObject}
* @return boolean
*/
private boolean isDocumentUserTagged(final List<DynamicObject> tags) {
return tags != null && tags.stream().anyMatch(t -> {
String key = t.getString("key");
return key != null && !SYSTEM_DEFINED_TAGS.contains(key);
});
}

@Override
public boolean isFolderExists(final String siteId, final String path) {

Expand Down Expand Up @@ -1858,7 +1831,7 @@ private void saveDocument(final Map<String, AttributeValue> keys, final String s

Date now = new Date();
DynamodbRecordTx tx = getSaveDocumentAttributesTx(siteId, document.getDocumentId(), attributes,
AttributeValidationType.FULL, options.getValidationAccess(), now);
AttributeValidationType.FULL, options.getValidationAccess());

boolean isPathChanged = isPathChanged(siteId, document, previous, documentValues, writeBuilder);

Expand Down Expand Up @@ -1951,9 +1924,8 @@ public void saveDocumentAttributes(final String siteId, final String documentId,
final AttributeValidationType validation, final AttributeValidationAccess validationAccess)
throws ValidationException {

Date now = new Date();
DynamodbRecordTx tx = getSaveDocumentAttributesTx(siteId, documentId, attributes, validation,
validationAccess, now);
DynamodbRecordTx tx =
getSaveDocumentAttributesTx(siteId, documentId, attributes, validation, validationAccess);

saveDocumentAttributes(siteId, documentId, tx);
}
Expand All @@ -1980,13 +1952,13 @@ private void saveDocumentAttributes(final String siteId, final String documentId
}

private DynamodbRecordTx getSaveDocumentAttributesTx(final String siteId, final String documentId,
final Collection<DocumentAttributeRecord> allAttributes,
final AttributeValidationType validation, final AttributeValidationAccess validationAccess,
final Date now) throws ValidationException {
final Collection<DocumentAttributeRecord> newAttributes,
final AttributeValidationType validation, final AttributeValidationAccess validationAccess)
throws ValidationException {

DynamodbRecordTx tx;

if (allAttributes != null) {
if (newAttributes != null) {

DocumentAttributeRecordListBuilder listBuilder = new DocumentAttributeRecordListBuilder();

Expand All @@ -1996,14 +1968,11 @@ private DynamodbRecordTx getSaveDocumentAttributesTx(final String siteId, final
List<DocumentAttributeRecord> previousAttributes =
previousAllAttributes.stream().filter(Predicate.not(PREDICIATE_COMPOSITE_KEY)).toList();

Collection<DocumentAttributeRecord> newAttributes = isSet(validationAccess) ? allAttributes
: filterAttributesByPrevious(allAttributes, previousAttributes);

List<DocumentAttributeRecord> previousCompositeKeys =
previousAllAttributes.stream().filter(PREDICIATE_COMPOSITE_KEY).toList();

Collection<DocumentAttributeRecord> attributesToBeDeleted =
getAttributesToBeDeleted(validationAccess, allAttributes, previousAttributes);
getAttributesToBeDeleted(validationAccess, newAttributes, previousAttributes);

listBuilder.setNewAttributes(newAttributes);
listBuilder.setPreviousAttributes(previousAttributes);
Expand All @@ -2021,10 +1990,20 @@ private DynamodbRecordTx getSaveDocumentAttributesTx(final String siteId, final
Objects.concat(compositeKeysToBeDeleted, listBuilder.getCompositeKeysToBeDeleted());

// reset inserted date to match
Date now = new Date();
toSave.forEach(t -> t.setInsertedDate(now));

tx = new DynamodbRecordTx(toSave,
Objects.concat(attributesToBeDeleted, compositeKeysToBeDeleted));
Collection<DocumentAttributeRecord> toBeDeleted =
concat(attributesToBeDeleted, compositeKeysToBeDeleted).stream()
.filter(Predicate.not(new DynamodbRecordKeyPredicate(toSave))).toList();

if (!toBeDeleted.isEmpty() && toSave.isEmpty()) {
throw new ValidationException(List.of(new ValidationErrorImpl()
.error("No attributes found to be saved, only found ones to delete")));
}

tx = new DynamodbRecordTx(toSave, toBeDeleted);

} else {
tx = new DynamodbRecordTx(Collections.emptyList(), Collections.emptyList());
}
Expand All @@ -2048,30 +2027,6 @@ private void addDocumentSyncRecord(final WriteRequestBuilder writeBuilder, final
}
}

private boolean isSet(final AttributeValidationAccess validationAccess) {
return AttributeValidationAccess.SET.equals(validationAccess)
|| AttributeValidationAccess.ADMIN_SET.equals(validationAccess);
}

/**
* Filter out Composite Keys and any previous attributes that match new attributes.
*
* @param newAttributes {@link Collection} {@link DocumentAttributeRecord}
* @param previousAttributes {@link Collection} {@link DocumentAttributeRecord}
* @return {@link Collection} {@link DocumentAttributeRecord}
*/
private Collection<DocumentAttributeRecord> filterAttributesByPrevious(
final Collection<DocumentAttributeRecord> newAttributes,
final Collection<DocumentAttributeRecord> previousAttributes) {

Set<String> attributeKeys = previousAttributes.stream().map(DocumentAttributeRecord::getKey)
.collect(Collectors.toSet());
Set<String> keys = createAttributeKeys(previousAttributes);

return newAttributes.stream().filter(a -> !attributeKeys.contains(a.getKey())
|| isDocumentAttributeKeyMatchPredicate(a, attributeKeys, keys)).toList();
}

private Collection<DocumentAttributeRecord> getCompositeKeysToBeDeletedByAttributes(
final Collection<DocumentAttributeRecord> previousCompositeKeys,
final Collection<DocumentAttributeRecord> attributesToBeDeleted) {
Expand Down Expand Up @@ -2112,28 +2067,15 @@ private Collection<DocumentAttributeRecord> getAttributesToBeDeleted(
attributesToBeDeleted =
previousAttributes.stream().filter(Predicate.not(PREDICIATE_COMPOSITE_KEY))
.filter(a -> isDocumentAttributeKeyMatchPredicate(a, attributeKeys, keys)).toList();
}

// when setting attributes remove existing attribute keys
if (isSetAccess(validationAccess) || isSetItemAccess(validationAccess)) {
} else if (isSetAccess(validationAccess)) {
// when setting attributes remove existing attribute keys
attributesToBeDeleted = previousAttributes;

} else if (isSetItemAccess(validationAccess)) { // remove all keys associated with the attribute
Set<String> attributeKeys =
attributes.stream().map(DocumentAttributeRecord::getKey).collect(Collectors.toSet());
Set<String> keys = createAttributeKeys(attributes);

// delete existing attribute keys, but if the key is exactly the same key/value as existing,
// don't delete
if (isSetAccess(validationAccess)) {
attributesToBeDeleted =
previousAttributes.stream().filter(Predicate.not(PREDICIATE_COMPOSITE_KEY))
.filter(a -> !attributeKeys.contains(a.getKey())
|| isDocumentAttributeKeyMatchPredicate(a, attributeKeys, keys))
.toList();
} else {
attributesToBeDeleted =
previousAttributes.stream().filter(Predicate.not(PREDICIATE_COMPOSITE_KEY))
.filter(a -> attributeKeys.contains(a.getKey())).toList();
}
attributesToBeDeleted =
previousAttributes.stream().filter(a -> attributeKeys.contains(a.getKey())).toList();
}

return attributesToBeDeleted;
Expand Down Expand Up @@ -2536,7 +2478,9 @@ private void validateDocumentAttributesExist(final String siteId, final String d
if (AttributeValidationAccess.CREATE.equals(validationAccess)
|| AttributeValidationAccess.ADMIN_CREATE.equals(validationAccess)) {

Set<String> keys = documentAttributes.stream().map(DocumentAttributeRecord::getKey)
Set<String> keys = documentAttributes.stream()
.filter(a -> !DocumentAttributeValueType.COMPOSITE_STRING.equals(a.getValueType()))
.map(DocumentAttributeRecord::getKey)
.filter(k -> !AttributeKeyReserved.RELATIONSHIPS.getKey().equals(k))
.collect(Collectors.toSet());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,58 @@ public void testPutDocumentAttribute04() throws ApiException {
}
}

/**
* PUT /documents/{documentId}/attributes/{attributeKey} with the same values.
*
* @throws ApiException ApiException
*/
@Test
public void testPutDocumentAttribute05() throws ApiException {
// given
for (String siteId : Arrays.asList(null, SITE_ID)) {

setBearerToken(siteId);
addAttribute(siteId, "c0", null, null);

AddDocumentUploadRequest docReq = new AddDocumentUploadRequest();
AddDocumentAttribute attr0 = new AddDocumentAttribute(new AddDocumentAttributeStandard()
.key("c0").addStringValuesItem("111").addStringValuesItem("222"));
docReq.addAttributesItem(attr0);

String documentId =
this.documentsApi.addDocumentUpload(docReq, siteId, null, null, null).getDocumentId();

SetDocumentAttributeRequest req = new SetDocumentAttributeRequest().attribute(
new AddDocumentAttributeValue().addStringValuesItem("111").addStringValuesItem("222"));

// when
SetResponse response =
this.documentAttributesApi.setDocumentAttributeValue(documentId, "c0", req, siteId);

// then
assertEquals("Updated attribute 'c0' on document '" + documentId + "'",
response.getMessage());

DocumentAttribute c0 = getDocumentAttribute(siteId, documentId, "c0");
assertNotNull(c0);
assertEquals("c0", c0.getKey());
assertEquals("111,222", String.join(",", notNull(c0.getStringValues())));

// given
req = new SetDocumentAttributeRequest()
.attribute(new AddDocumentAttributeValue().addStringValuesItem("111"));

// when
this.documentAttributesApi.setDocumentAttributeValue(documentId, "c0", req, siteId);

// then
c0 = getDocumentAttribute(siteId, documentId, "c0");
assertNotNull(c0);
assertEquals("c0", c0.getKey());
assertEquals("111", c0.getStringValue());
}
}

/**
* PATCH /documents, POST /documents/{documentId}/attributes, PUT
* /documents/{documentId}/attributes, existing attribute .
Expand Down

0 comments on commit ca2d09c

Please sign in to comment.