Java tutorial
/* $Id: 0390bb4f07615e0e7cd83d24d26cc81cb045344b $ * * Part of ZonMW project no. 50-53000-98-156 * * @license * 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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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. * * Copyright (c) 2016 RIVM National Institute for Health and Environment */ package io.coala.enterprise; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.transaction.Transactional; import com.eaio.uuid.UUID; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.util.StdConverter; import io.coala.bind.LocalBinder; import io.coala.bind.LocalId; import io.coala.enterprise.persist.FactDao; import io.coala.exception.Thrower; import io.coala.json.JsonUtil; import io.coala.log.LogUtil; import io.coala.log.LogUtil.Pretty; import io.coala.name.Identified; import io.coala.persist.JPAUtil; import io.coala.persist.Persistable; import io.coala.time.Instant; import io.coala.util.ReflectUtil; import rx.Observer; /** * {@link Fact} * * @version $Id: 0390bb4f07615e0e7cd83d24d26cc81cb045344b $ * @author Rick van Krevelen */ /* * @JsonAutoDetect( fieldVisibility = Visibility.ANY, getterVisibility = * Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = * Visibility.NONE ) */ public interface Fact extends Identified.Ordinal<Fact.ID>, Persistable<FactDao> { String TRANSACTION_PROPERTY = "transaction"; String KIND_PROPERTY = "kind"; String OCCUR_PROPERTY = "occur"; String OCCUR_POSIX_PROPERTY = "occurPosixSec"; String EXPIRE_PROPERTY = "expire"; String EXPIRE_POSIX_PROPERTY = "expirePosixSec"; String CAUSE_REF_PROPERTY = "causeRef"; /** @return */ @JsonProperty(TRANSACTION_PROPERTY) <F extends Fact> Transaction<F> transaction(); <F extends Fact> F self(); // "this" points to impl, not proxy /** * Commit (i.e. save & send) this {@link Fact} * * @return the {@link Fact} again to allow chaining */ default <F extends Fact> F commit() { return commit(kind().isTerminal()); } /** * Commit (i.e. save & send) this {@link Fact} * * @param cleanUp {@code true} iff the {@link Transaction} may clean up, * e.g. it has terminated or no further facts are expected * @return the {@link Fact} again to allow chaining */ @SuppressWarnings("unchecked") default <F extends Fact> F commit(final boolean cleanUp) { return ((Transaction<F>) transaction()).commit(self(), cleanUp); } default Class<? extends Fact> type() { return Objects.requireNonNull(transaction()).kind(); } /** @return */ // derived @JsonIgnore default Actor.ID creatorRef() { return kind().originatorRoleType() == RoleKind.EXECUTOR ? transaction().executorRef() : transaction().initiatorRef(); } /** @return */ // derived @JsonIgnore default Actor.ID responderRef() { return kind().responderRoleKind() == RoleKind.EXECUTOR ? transaction().executorRef() : transaction().initiatorRef(); } /** * @return {@code true} iff {@link #creator()} and {@link #responder()} are * in the same {@link Actor.ID#organization()} */ @JsonIgnore // derived default boolean isIncoming() { return creatorRef().organization().equals(responderRef().organization()); } /** * @return {@code true} iff {@link #creator()} and {@link #responder()} are * in a different {@link Actor.ID#organization()} */ @JsonIgnore // derived default boolean isOutgoing() { return !isIncoming(); } /** @return */ @JsonProperty(KIND_PROPERTY) FactKind kind(); /** @return */ @JsonProperty(OCCUR_PROPERTY) Instant occur(); /** @return */ @JsonProperty(OCCUR_POSIX_PROPERTY) default java.time.Instant occurUtc() { return occur().toDate(transaction().offset()); } /** @return */ @JsonProperty(EXPIRE_PROPERTY) Instant expire(); /** @return */ @JsonProperty(EXPIRE_POSIX_PROPERTY) default java.time.Instant expirePosixSec() { return expire() == null ? null : expire().toDate(transaction().offset()); } /** @return */ @JsonProperty(CAUSE_REF_PROPERTY) Fact.ID causeRef(); /** * Default storage for bean properties, also useful for reference by a * {@link JsonAnyGetter} or {@link JsonAnySetter} method * * @return the properties {@link Map} as used for extended getters/setters */ Map<String, Object> properties(); /** * Useful as {@link JsonAnySetter} * * @param property the property (or bean attribute) to change * @param value the new value * @return the previous value, as per {@link Map#put(Object, Object)} */ default Object set(final String property, final Object value) { return properties().put(property, value); } /** * Builder-style bean property setter * * @param property the property (or bean attribute) to change * @param value the new value * @return this {@link Fact} to allow chaining */ @SuppressWarnings("unchecked") @JsonIgnore default <F extends Fact> F with(final String property, final Object value) { set(property, value); return (F) this; } /** * Semi-{@link Transactional} persistence short-hand utility method * * @param em the {@link EntityManager} to use for persistence calls * @param binder the {@link LocalBinder} to use for restoring results * @param query the JPQL query, e.g. {@code "SELECT f FROM "} * @return a {@link Stream} of all matching {@link Fact}s */ default Stream<Fact> find(final EntityManager em, final LocalBinder binder, final String query) { return em.createQuery(query, FactDao.class).getResultList().stream().map(dao -> dao.restore(binder)); } /** * Semi-{@link Transactional} persistence short-hand utility method * * @param em the {@link EntityManager} to use for persistence calls * @param binder the {@link LocalBinder} to use for restoring results * @return a {@link Stream} of all known {@link Fact}s */ default Stream<Fact> findAll(final EntityManager em, final LocalBinder binder) { return find(em, binder, "SELECT dao FROM " + FactDao.ENTITY_NAME + " dao"); } /** * @param subtype the type of {@link Fact} to mimic * @return the {@link Proxy} instance */ @SuppressWarnings("unchecked") default <F extends Fact> F proxyAs(final Class<F> subtype) { return proxyAs(subtype, null); } /** * @param subtype the type of {@link Fact} to mimic * @param callObserver an {@link Observer} of method call, or {@code null} * @return the {@link Proxy} instance */ @SuppressWarnings("unchecked") default <F extends Fact> F proxyAs(final Class<F> subtype, final Observer<Method> callObserver) { return proxyAs(this, subtype, callObserver); } /** * @param subtype the type of {@link Fact} to mimic * @param callObserver an {@link Observer} of method call, or {@code null} * @return the {@link Proxy} instance */ @SuppressWarnings("unchecked") static <F extends Fact> F proxyAs(final Fact impl, final Class<F> subtype, final Observer<Method> callObserver) { final F proxy = (F) Proxy.newProxyInstance(subtype.getClassLoader(), new Class<?>[] { subtype }, (self, method, args) -> { try { final Object result = method.isDefault() && Proxy.isProxyClass(self.getClass()) ? ReflectUtil.invokeDefaultMethod(self, method, args) : method.invoke(impl, args); if (callObserver != null) callObserver.onNext(method); return result; } catch (Throwable e) { if (e instanceof IllegalArgumentException) try { return ReflectUtil.invokeAsBean(impl.properties(), subtype, method, args); } catch (final Exception ignore) { LogUtil.getLogger(Fact.class).warn("{}method call failed: {}", method.isDefault() ? "default " : "", method, ignore); } if (e instanceof InvocationTargetException) e = e.getCause(); if (callObserver != null) callObserver.onError(e); throw e; } }); return proxy; } // @JsonValue default String toJSON() { return JsonUtil.stringify(this); } ObjectMapper DEFAULT_OM = JsonUtil.getJOM(); static <F extends Fact> F fromJSON(final String json, final Class<F> factType) { return fromJSON(DEFAULT_OM, json, factType); } static <F extends Fact> F fromJSON(final TreeNode json, final Class<F> factType) { return fromJSON(DEFAULT_OM, json, factType); } static <F extends Fact> F fromJSON(final ObjectMapper om, final String json, final Class<F> factType) { try { return json == null ? null : fromJSON(om, om.readTree(json), factType); } catch (final Exception e) { e.printStackTrace(); return Thrower.rethrowUnchecked(e); } } static <F extends Fact> F fromJSON(final ObjectMapper om, final TreeNode json, final Class<F> factType) { if (json == null) return null; try { Simple.checkRegistered(om); final Simple result = JsonUtil.valueOf(om, json, Simple.class); fromJSON(om, json, factType, result.properties()); return result.proxyAs(factType, null); } catch (final Exception e) { return Thrower.rethrowUnchecked(e); } } /** * override and deserialize bean properties as declared in factType * <p> * TODO detect properties from builder methods: {@code withKey(T value)} * * @param om * @param json * @param factType * @param properties * @return the properties again, to allow chaining * @throws IntrospectionException */ static <T extends Fact> Map<String, Object> fromJSON(final ObjectMapper om, final TreeNode json, final Class<T> factType, final Map<String, Object> properties) { try { final ObjectNode tree = (ObjectNode) json; final BeanInfo beanInfo = Introspector.getBeanInfo(factType); for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) if (tree.has(pd.getName())) properties.computeIfPresent(pd.getName(), (property, current) -> JsonUtil.valueOf(om, tree.get(property), pd.getPropertyType())); return properties; } catch (final Throwable e) { return Thrower.rethrowUnchecked(e); } } @SuppressWarnings("unchecked") @Override default FactDao persist(final EntityManager em) { JPAUtil.existsOrCreate(em, () -> FactDao.exists(em, id()), () -> FactDao.create(em, self())); return null; } /** * {@link ID} * * @version $Id: 0390bb4f07615e0e7cd83d24d26cc81cb045344b $ * @author Rick van Krevelen */ @JsonDeserialize(converter = ID.FromStringConverter.class) class ID extends LocalId { public static class FromStringConverter extends StdConverter<String, ID> { @Override public ID convert(final String value) { return of(new UUID(value), null); } } @Override @JsonValue public String toJSON() { // omit parentRef return unwrap().toString(); } @Override public UUID unwrap() { return (UUID) super.unwrap(); } @Override public Transaction.ID parentRef() { return (Transaction.ID) super.parentRef(); } public Pretty prettyHash() { return LogUtil.wrapToString(() -> Integer.toHexString(unwrap().hashCode())); } /** @return an {@link ID} with specified {@link UUID} */ public static ID of(final UUID value, final Transaction.ID ctx) { return LocalId.of(new ID(), value, ctx); } /** @return a new {@link ID} */ public static ID create(final Transaction.ID ctx) { return of(new UUID(), ctx); } } /** * {@link Simple} * * @version $Id: 0390bb4f07615e0e7cd83d24d26cc81cb045344b $ * @author Rick van Krevelen */ @JsonInclude(Include.NON_NULL) class Simple implements Fact { private static Map<ObjectMapper, Module> TX_MODULE_CACHE = new HashMap<>(); /** * @param om */ public static void checkRegistered(ObjectMapper om) { JsonUtil.checkRegisteredMembers(om, Simple.class); TX_MODULE_CACHE.computeIfAbsent(om, key -> { final SimpleModule result = new SimpleModule(Transaction.class.getSimpleName(), new Version(1, 0, 0, null, null, null)); result.addAbstractTypeMapping(Transaction.class, JsonUtil.checkRegisteredMembers(om, Transaction.Simple.class)); om.registerModule(result); return result; }); } private transient Fact proxy = null; private Fact.ID id; private Instant occurrence; private Transaction<?> transaction; private FactKind kind; private Instant expiration; private Fact.ID causeRef; private Map<String, Object> properties = new HashMap<>(); /** * {@link Simple} zero-arg bean constructor */ protected Simple() { } protected Simple(final Fact.ID id, final Instant occurrence, final Transaction<?> transaction, final FactKind kind, final Instant expiration, final Fact.ID causeRef, final Map<?, ?>... properties) { this.id = id; this.occurrence = occurrence; this.transaction = transaction; this.kind = kind; this.expiration = expiration; this.causeRef = causeRef; if (properties != null) for (Map<?, ?> map : properties) map.forEach((key, value) -> set(key.toString(), value)); } @Override public String toString() { return type().getSimpleName() + '[' + Integer.toHexString(id().unwrap().hashCode()) + '|' + kind() + '|' + creatorRef() + '|' + occur() + ']' + properties(); } @Override public int hashCode() { return Identified.hashCode(this); } @Override public Fact.ID id() { return this.id; } @Override public Instant occur() { return this.occurrence; } @SuppressWarnings("unchecked") @Override public Transaction<?> transaction() { return this.transaction; } @Override public FactKind kind() { return this.kind; } @Override public Instant expire() { return this.expiration; } @Override public Fact.ID causeRef() { return this.causeRef; } @Override @JsonAnyGetter public Map<String, Object> properties() { return this.properties; } @Override @JsonAnySetter public Object set(final String key, final Object value) { // if( key.toString().equals( "h" ) ) // new IllegalStateException( "put " + key + "=" + value ) // .printStackTrace(); return properties().put(key, value); } @SuppressWarnings("unchecked") @Override public <F extends Fact> F self() { return (F) this.proxy; } /** * @param subtype the type of {@link Fact} to mimic * @param callObserver an {@link Observer} of method call, or * {@code null} * @return the {@link Proxy} instance */ @Override @SuppressWarnings("unchecked") public <F extends Fact> F proxyAs(final Class<F> subtype, final Observer<Method> callObserver) { final F proxy = Fact.proxyAs(this, subtype, callObserver); this.proxy = proxy; return proxy; } } /** * {@link Factory} * * @version $Id: 0390bb4f07615e0e7cd83d24d26cc81cb045344b $ * @author Rick van Krevelen */ interface Factory { LocalId ownerRef(); FactBank<?> factBank(); /** * @param tranKind the type of {@link Fact} (transaction kind) * @param id the {@link Fact.ID} * @param transaction the {@link Transaction} * @param factKind the {@link FactKind} (process step kind) * @param expiration the {@link Instant} of expiration * @param causeRef the cause {@link Fact.ID}, or {@code null} for * external initiation * @param properties property {@link Map mappings}, if any * @return a {@link Fact} */ default <F extends Fact> F create(Class<F> tranKind, Fact.ID id, Transaction<? super F> transaction, FactKind factKind, Instant expiration, Fact.ID causeRef, Map<?, ?>... properties) { return create(tranKind, id, transaction, factKind, transaction.now(), expiration, causeRef, properties); } /** * @param tranKind the type of {@link Fact} (transaction kind) * @param id the {@link Fact.ID} * @param transaction the {@link Transaction} * @param factKind the {@link FactKind} (process step kind) * @param occurrence the {@link Instant} of occurrence * @param expiration the {@link Instant} of expiration * @param causeRef the cause {@link Fact.ID}, or {@code null} for * external initiation * @param properties property {@link Map mappings}, if any * @return a {@link Fact} */ <F extends Fact> F create(Class<F> tranKind, Fact.ID id, Transaction<? super F> transaction, FactKind factKind, Instant occurrence, Instant expiration, Fact.ID causeRef, Map<?, ?>... properties); /** * {@link SimpleProxies} generates the desired extension of {@link Fact} * as proxy decorating a new {@link Fact.Simple} instance */ @Singleton class SimpleProxies implements Factory { @Inject private LocalBinder binder; @SuppressWarnings("rawtypes") @Inject private FactBank.Factory bankFactory; @Override public <F extends Fact> F create(final Class<F> tranKind, final Fact.ID id, final Transaction<? super F> transaction, final FactKind factKind, final Instant occurrence, final Instant expiration, final Fact.ID causeRef, final Map<?, ?>... params) { return new Simple(id, occurrence, transaction, factKind, expiration, causeRef, params) .proxyAs(tranKind, null); } @Override public LocalId ownerRef() { return this.binder.id(); } @SuppressWarnings("unchecked") @Override public FactBank<?> factBank() { return this.bankFactory.create(); } } } }