All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDelimited Maven / Gradle / Ivy

/*
 * Copyright 2016-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *    http://aws.amazon.com/apache2.0
 *
 * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and
 * limitations under the License.
 */
package com.amazonaws.services.dynamodbv2.datamodeling;

import com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties.Bean;
import com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties.BeanMap;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;

/**
 * Annotation to convert an object into a single delimited {@link String}
 * attribute.
 *
 * 
 * @DynamoDBDelimited(
 *     attributeNames={"areaCode","exchange","subscriber"},
 *     delimiter='-'
 * )
 * public PhoneNumber getPhoneNumber()
 * 
* *

Where,

*
 * public class PhoneNumber {
 *     private String areaCode;
 *     private String exchange;
 *     private String subscriber;
 *
 *     public String getAreaCode() { return areaCode; }
 *     public void setAreaCode(String areaCode) { this.areaCode = areaCode; }
 *
 *     public String getExchange() { return exchange; }
 *     public void setExchange(String exchange) { this.exchange = exchange; }
 *
 *     public String getSubscriber() { return subscriber; }
 *     public void setSubscriber(String subscriber) { this.subscriber = subscriber; }
 * }
 * 
* *

Would write,

*
    *
  • PhoneNumber("206","266","1000") = "206-266-1000"
  • *
  • PhoneNumber("206",null,"1000") = "206--1000"
  • *
  • PhoneNumber("206",null,null) = "206--"
  • *
  • PhoneNumber(null,"266","1000") = "-266-1000"
  • *
  • PhoneNumber(null,"266",null) = "-266-"
  • *
  • PhoneNumber(null,null,"1000") = "--1000"
  • *
  • PhoneNumber(null,null,null) = null
  • *
  • null = null
  • *
* * Conversely, reading not fully formatted values from DynamoDB given, *
    *
  • "" = empty string not allowed by DDB but would produce empty object
  • *
  • "--" = PhoneNumber(null,null,null)
  • *
  • "-----" = PhoneNumber(null,null,null)
  • *
  • "206" = PhoneNumber("206",null,null)
  • *
  • "206-266" = PhoneNumber("206","266",null)
  • *
  • "206-266-1000-1234-5678" = PhoneNumber("206","266","1000")
  • *
* *

The converter does not protect against values which may also contain the * delimiter. If more advanced conversion is required, consider implementing, * a custom {@link DynamoDBTypeConverter}.

* *

New delimited values may always be appended to the string, however, there * are some risks in distributed systems where, if one system has updated * delimiting instructions and begins to persist new values, other systems, * which also persist that same data, would effectively truncate it back to the * original format.

* *

Auto-generated annotations are not supported on field/property.

* *

TYpe-converted annotations, annotated by {@link DynamoDBTypeConverted}, * where the output type is {@link String} are supported. * *

May be used as a meta-annotation.

*/ @DynamoDB @DynamoDBTypeConverted(converter=DynamoDBDelimited.Converter.class) @DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.S) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interface DynamoDBDelimited { /** * The delimiter for separating attribute values; default is |. */ char delimiter() default '|'; /** * The ordered list of attribute/field names. */ String[] attributeNames(); /** * Type converter for string delimited attributes. */ static final class Converter implements DynamoDBTypeConverter { private final Field[] fields; private final Class targetType; private final String delimiter; public Converter(Class targetType, DynamoDBDelimited annotation) { final BeanMap beans = new BeanMap(targetType, true); final String[] names = annotation.attributeNames(); if (names.length <= 1) { throw new DynamoDBMappingException(targetType + " missing attributeNames in @DynamoDBDelimited; must specify two or more attribute names"); } this.delimiter = String.valueOf(annotation.delimiter()); this.fields = new Field[names.length]; this.targetType = targetType; for (int i = 0; i < fields.length; i ++) { if (beans.containsKey(names[i]) == false) { throw new DynamoDBMappingException(targetType + " does not map %s on model " + names[i]); } this.fields[i] = new Field(targetType, beans.get(names[i])); } } @Override public final String convert(final T object) { final StringBuilder string = new StringBuilder(); for (int i = 0; i < fields.length; i++) { if (i > 0) { string.append(delimiter); } final String value = fields[i].get(object); if (value != null) { if (value.contains(delimiter)) { throw new DynamoDBMappingException(String.format( "%s[%s] field value \"%s\" must not contain delimiter %s", targetType, fields[i].bean.properties().attributeName(), value, delimiter )); } string.append(value); } } return string.length() < fields.length ? null : string.toString(); } @Override public final T unconvert(final String string) { final T object = StandardBeanProperties.DeclaringReflect.newInstance(targetType); final String[] values = string.split(Pattern.quote(delimiter)); for (int i = 0, its = Math.min(fields.length, values.length); i < its; i++) { fields[i].set(object, values[i]); } return object; } private static final class Field { private final DynamoDBTypeConverter converter; private final Bean bean; private Field(final Class type, final Bean bean) { if (bean.type().typeConverter() == null) { this.converter = StandardTypeConverters.factory().getConverter(String.class, bean.type().targetType()); } else { this.converter = bean.type().typeConverter(); } this.bean = bean; } private final String get(final T object) { final V value = bean.reflect().get(object); if (value == null) { return null; } return converter.convert(value); } private final void set(final T object, final String string) { if (!string.isEmpty()) { final V value = converter.unconvert(string); if (value != null) { bean.reflect().set(object, value); } } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy