diff --git a/src/main/java/pl/ais/commons/domain/security/CryptographicService.java b/src/main/java/pl/ais/commons/domain/security/CryptographicService.java new file mode 100644 index 0000000..8e5c1d2 --- /dev/null +++ b/src/main/java/pl/ais/commons/domain/security/CryptographicService.java @@ -0,0 +1,34 @@ +package pl.ais.commons.domain.security; + +import java.io.Serializable; + +import javax.annotation.Nonnull; + +/** + * Defines the API contract for the cryptographic service. + * + * @param defines the type of unencrypted value + * @author Warlock, AIS.PL + * @since 1.0.2 + */ +public interface CryptographicService extends Serializable { + + /** + * Decrypts the value. + * + * @param value the value to decrypt + * @return decrypted value + */ + @Nonnull + T decrypt(@Nonnull DecryptableValue value); + + /** + * Encrypts given value. + * + * @param value the value to encrypt + * @return encrypted value + */ + @Nonnull + DecryptableValue encrypt(@Nonnull T value); + +} diff --git a/src/main/java/pl/ais/commons/domain/security/DecryptableValue.java b/src/main/java/pl/ais/commons/domain/security/DecryptableValue.java new file mode 100644 index 0000000..2184c10 --- /dev/null +++ b/src/main/java/pl/ais/commons/domain/security/DecryptableValue.java @@ -0,0 +1,97 @@ +package pl.ais.commons.domain.security; + +import java.io.Serializable; +import java.util.Arrays; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import pl.ais.commons.domain.stereotype.ValueObject; + +/** + * Decryptable value. + * + * @param defines the type of unencrypted value + * @author Warlock, AIS.PL + * @since 1.0.2 + */ +@ValueObject +public final class DecryptableValue implements Serializable { + + private transient T decryptedValue; + + private final CryptographicService decryptor; + + private final byte[] encryptedValue; + + /** + * Constructs new instance. + * + * @param decryptor decryptor which will be used to decrypt value + * @param encryptedValue encrypted value, which will be enclosed by the created instance + */ + public DecryptableValue(@Nonnull final CryptographicService decryptor, @Nonnull final byte[] encryptedValue) { + super(); + if ((null == decryptor) || (null == encryptedValue)) { + throw new IllegalArgumentException("Both decryptor and encrypted value cannot be null."); + } + this.decryptor = decryptor; + this.encryptedValue = Arrays.copyOf(encryptedValue, encryptedValue.length); + } + + /** + * Decrypts the value. + * + * @return decrypted value + */ + @Nonnull + public T decrypt() { + if (null == decryptedValue) { + decryptedValue = decryptor.decrypt(this); + } + return decryptedValue; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @SuppressWarnings("rawtypes") + @Override + public boolean equals(final Object object) { + boolean result = (this == object); + if (!result && (null != object) && (getClass() == object.getClass())) { + final DecryptableValue other = (DecryptableValue) object; + result = new EqualsBuilder().append(decryptor, other.decryptor) + .append(encryptedValue, other.encryptedValue).isEquals(); + } + return result; + } + + /** + * @return the encrypted value + */ + @Nonnull + public byte[] getEncryptedValue() { + return Arrays.copyOf(encryptedValue, encryptedValue.length); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return new HashCodeBuilder().append(decryptor).append(encryptedValue).toHashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return new ToStringBuilder(this).append("decryptor", decryptor).build(); + } + +} diff --git a/src/main/java/pl/ais/commons/domain/security/PassThroughCryptographicService.java b/src/main/java/pl/ais/commons/domain/security/PassThroughCryptographicService.java new file mode 100644 index 0000000..f3472db --- /dev/null +++ b/src/main/java/pl/ais/commons/domain/security/PassThroughCryptographicService.java @@ -0,0 +1,103 @@ +package pl.ais.commons.domain.security; + +import java.nio.charset.Charset; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import pl.ais.commons.domain.stereotype.DomainService; + +/** + * Pass-through implementation of cryptographic service. + * + *

+ * This implementation in fact doesn't perform any encryption. Returned {@link DecryptableValue} holds + * the unencrypted data passed through. + *

+ * + * @author Warlock, AIS.PL + * @since 1.0.2 + */ +@ThreadSafe +@DomainService +public final class PassThroughCryptographicService implements CryptographicService { + + private final String charsetName; + + /** + * Constructs new instance using default charset of this JVM for the string conversions. + */ + public PassThroughCryptographicService() { + this(Charset.defaultCharset()); + } + + /** + * Constructs new instance. + * + * @param charset the charset which should be used for the string conversions + */ + public PassThroughCryptographicService(@Nonnull final Charset charset) { + super(); + if (null == charset) { + throw new IllegalArgumentException("Charset cannot be null."); + } + this.charsetName = charset.name(); + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public String decrypt(@Nonnull final DecryptableValue value) { + if (null == value) { + throw new IllegalArgumentException(); + } + return new String(value.getEncryptedValue(), Charset.forName(charsetName)); + } + + /** + * {@inheritDoc} + */ + @Override + @Nonnull + public DecryptableValue encrypt(@Nonnull final String value) { + if (null == value) { + throw new IllegalArgumentException(); + } + return new DecryptableValue<>(this, value.getBytes(Charset.forName(charsetName))); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object object) { + boolean result = (this == object); + if (!result && (null != object) && (getClass() == object.getClass())) { + final PassThroughCryptographicService other = (PassThroughCryptographicService) object; + result = charsetName.equals(other.charsetName); + } + return result; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return new HashCodeBuilder().append(charsetName).toHashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return new ToStringBuilder(this).append("charsetName", charsetName).build(); + } + +} diff --git a/src/main/java/pl/ais/commons/domain/stereotype/DomainService.java b/src/main/java/pl/ais/commons/domain/stereotype/DomainService.java new file mode 100644 index 0000000..95a624c --- /dev/null +++ b/src/main/java/pl/ais/commons/domain/stereotype/DomainService.java @@ -0,0 +1,34 @@ +package pl.ais.commons.domain.stereotype; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Service; + +/** + * Indicates that an annotated class is a Domain Service. + * + * @author Warlock, AIS.PL + * @since 1.0.2 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Service +@Scope(proxyMode = ScopedProxyMode.INTERFACES) +public @interface DomainService { + + /** + * The value may indicate a suggestion for a logical component name, + * to be turned into a Spring bean in case of an autodetected component. + * + * @return the suggested component name, if any + */ + String value() default ""; + +} diff --git a/src/main/java/pl/ais/commons/domain/stereotype/ValueObject.java b/src/main/java/pl/ais/commons/domain/stereotype/ValueObject.java new file mode 100644 index 0000000..8764e1a --- /dev/null +++ b/src/main/java/pl/ais/commons/domain/stereotype/ValueObject.java @@ -0,0 +1,28 @@ +package pl.ais.commons.domain.stereotype; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.concurrent.Immutable; + +/** + * Indicates that an annotated class is a Value Object. + * + * @see Value Object (Patterns of Enterprise Application Architecture) + * @see Value Object (Wikipedia) + * + * @author Warlock, AIS.PL + * @since 1.0.2 + */ +@Documented +@Immutable +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ValueObject { + + // Empty by design ... + +}