diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java index a86607801da..b4fe6344785 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java @@ -29,22 +29,20 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.interceptor.api.Hook; import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor; import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy; import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionStrategyEvaluator; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.SubscriptionUtil; import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.instance.model.Subscription; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,8 +74,6 @@ public class SubscriptionActivatingInterceptor { private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class); private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest; - private static final String REQUESTED_STATUS = Subscription.SubscriptionStatus.REQUESTED.toCode(); - private static final String ACTIVE_STATUS = Subscription.SubscriptionStatus.ACTIVE.toCode(); @Autowired private PlatformTransactionManager myTransactionManager; @@ -93,8 +89,6 @@ public class SubscriptionActivatingInterceptor { @Autowired private SubscriptionCanonicalizer mySubscriptionCanonicalizer; @Autowired - private MatchUrlService myMatchUrlService; - @Autowired private DaoConfig myDaoConfig; @Autowired private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; @@ -104,7 +98,7 @@ public class SubscriptionActivatingInterceptor { // subscriber applies.. String subscriptionChannelTypeCode = myFhirContext .newTerser() - .getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class) + .getSingleValueOrNull(theSubscription, SubscriptionConstants.SUBSCRIPTION_TYPE, IPrimitiveType.class) .getValueAsString(); Subscription.SubscriptionChannelType subscriptionChannelType = Subscription.SubscriptionChannelType.fromCode(subscriptionChannelTypeCode); @@ -113,10 +107,9 @@ public class SubscriptionActivatingInterceptor { return false; } - final IPrimitiveType status = myFhirContext.newTerser().getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class); - String statusString = status.getValueAsString(); + String statusString = mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription); - if (REQUESTED_STATUS.equals(statusString)) { + if (SubscriptionConstants.REQUESTED_STATUS.equals(statusString)) { if (TransactionSynchronizationManager.isSynchronizationActive()) { /* * If we're in a transaction, we don't want to try and change the status from @@ -133,7 +126,7 @@ public class SubscriptionActivatingInterceptor { Future activationFuture = myTaskExecutor.submit(new Runnable() { @Override public void run() { - activateSubscription(ACTIVE_STATUS, theSubscription, REQUESTED_STATUS); + activateSubscription(SubscriptionConstants.ACTIVE_STATUS, theSubscription, SubscriptionConstants.REQUESTED_STATUS); } }); @@ -152,9 +145,9 @@ public class SubscriptionActivatingInterceptor { }); return true; } else { - return activateSubscription(ACTIVE_STATUS, theSubscription, REQUESTED_STATUS); + return activateSubscription(SubscriptionConstants.ACTIVE_STATUS, theSubscription, SubscriptionConstants.REQUESTED_STATUS); } - } else if (ACTIVE_STATUS.equals(statusString)) { + } else if (SubscriptionConstants.ACTIVE_STATUS.equals(statusString)) { return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription); } else { // Status isn't "active" or "requested" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java index 94d4fc65600..d5d5c665305 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java @@ -48,8 +48,6 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class); private static final String SUBSCRIPTION_MATCHING_CHANNEL_NAME = "subscription-matching"; - static final String SUBSCRIPTION_STATUS = "Subscription.status"; - static final String SUBSCRIPTION_TYPE = "Subscription.channel.type"; private SubscribableChannel myProcessingChannel; @Autowired diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 49c0b24cfbf..c19ed86448a 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -97,6 +97,11 @@ jetty-servlet test + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java index 7e846cf68a7..f82cd27d3ce 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -168,11 +169,13 @@ public class ResourceModifiedMessage implements IResourceMessage { @Override public String toString() { - String resourceId = myPayloadId; - if (resourceId == null) { - resourceId = myId; - } - return "ResourceModified Message { " + myOperationType + ", " + resourceId + "}"; + return new ToStringBuilder(this) + .append("myId", myId) + .append("myOperationType", myOperationType) + .append("mySubscriptionId", mySubscriptionId) +// .append("myPayload", myPayload) + .append("myPayloadId", myPayloadId) +// .append("myPayloadDecoded", myPayloadDecoded) + .toString(); } - } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java index cd0802bf645..c5572024a7c 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java @@ -29,11 +29,11 @@ import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -292,4 +292,13 @@ public class SubscriptionCanonicalizer { } meta.addTag().setSystem(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY).setCode(value).setDisplay(display); } + + public String getSubscriptionStatus(IBaseResource theSubscription) { + final IPrimitiveType status = myFhirContext.newTerser().getSingleValueOrNull(theSubscription, SubscriptionConstants.SUBSCRIPTION_STATUS, IPrimitiveType.class); + if (status == null) { + return null; + } + return status.getValueAsString(); + } + } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java index 66f4cd58d18..2aa9f2be379 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription.module.cache; * #L% */ +import org.hl7.fhir.instance.model.Subscription; + public class SubscriptionConstants { /** @@ -90,4 +92,8 @@ public class SubscriptionConstants { */ public static final int DELIVERY_EXECUTOR_QUEUE_SIZE = 1000; + public static final String SUBSCRIPTION_STATUS = "Subscription.status"; + public static final String SUBSCRIPTION_TYPE = "Subscription.channel.type"; + public static final String REQUESTED_STATUS = Subscription.SubscriptionStatus.REQUESTED.toCode(); + public static final String ACTIVE_STATUS = Subscription.SubscriptionStatus.ACTIVE.toCode(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java index 8fd70e7f358..bb67f790cca 100755 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java @@ -87,7 +87,6 @@ public class SubscriptionLoader { ourLog.debug("Starting sync subscriptions"); SearchParameterMap map = new SearchParameterMap(); map.add(Subscription.SP_STATUS, new TokenOrListParam() - // TODO KHS perhaps we should only be requesting ACTIVE subscriptions here?... .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode())) .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()))); map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java index 1ec1447ff51..a57bb0e3cf4 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.subscription.module.standalone; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber; @@ -46,6 +48,8 @@ public class StandaloneSubscriptionMessageHandler implements MessageHandler { SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; @Autowired SubscriptionRegistry mySubscriptionRegistry; + @Autowired + SubscriptionCanonicalizer mySubscriptionCanonicalizer; @Override public void handleMessage(Message theMessage) throws MessagingException { @@ -61,7 +65,10 @@ public class StandaloneSubscriptionMessageHandler implements MessageHandler { RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(resource); if (resourceDef.getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { - mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource); + String status = mySubscriptionCanonicalizer.getSubscriptionStatus(resource); + if (SubscriptionConstants.ACTIVE_STATUS.equals(status)) { + mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource); + } } mySubscriptionMatchingSubscriber.matchActiveSubscriptionsAndDeliver(theResourceModifiedMessage); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java index 413d639fa02..3a9884f5f23 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) @@ -54,4 +55,10 @@ public class ResourceDeliveryJsonMessage extends BaseJsonMessage theList) { StopWatch sw = new StopWatch(); while (theList.size() != theTarget && sw.getMillis() <= 16000) { @@ -37,4 +41,16 @@ public abstract class BaseSubscriptionDstu3Test extends BaseSubscriptionTest { fail("Size " + theList.size() + " is != target " + theTarget + " - Got: " + describeResults); } } + + protected long nextId() { + return mySubscriptionTestHelper.nextId(); + } + + protected Subscription makeActiveSubscription(String theCriteria, String thePayload, String theEndpoint) { + return mySubscriptionTestHelper.makeActiveSubscription(theCriteria, thePayload, theEndpoint); + } + + protected Subscription makeSubscriptionWithStatus(String theCriteria, String thePayload, String theEndpoint, Subscription.SubscriptionStatus status) { + return mySubscriptionTestHelper.makeSubscriptionWithStatus(theCriteria, thePayload, theEndpoint, status); + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java index 9a1996a127e..06cef734fbe 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java @@ -44,5 +44,4 @@ public abstract class BaseSubscriptionTest { myMockFhirClientSubscriptionProvider.setBundleProvider(theBundleProvider); mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); } - } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/FhirObjectPrinter.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/FhirObjectPrinter.java index 0049bef852f..874157a9546 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/FhirObjectPrinter.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/FhirObjectPrinter.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.jpa.subscription.module; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryJsonMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.function.Function; @@ -11,16 +9,7 @@ public class FhirObjectPrinter implements Function { public String apply(Object object) { if (object instanceof IBaseResource) { IBaseResource resource = (IBaseResource) object; - return "Resource " + resource.getIdElement().getValue(); - } else if (object instanceof ResourceDeliveryMessage) { - ResourceDeliveryMessage resourceDeliveryMessage = (ResourceDeliveryMessage) object; - // TODO KHS move this to ResourceModifiedMessage.toString() - return "ResourceDelivery Message { " + resourceDeliveryMessage.getPayloadId() + "}"; - } else if (object instanceof ResourceDeliveryJsonMessage) { - ResourceDeliveryJsonMessage resourceDeliveryJsonMessage = (ResourceDeliveryJsonMessage) object; - // TODO KHS move this to ResourceModifiedMessage.toString() - ResourceDeliveryMessage resourceDeliveryMessage = resourceDeliveryJsonMessage.getPayload(); - return "ResourceDeliveryJsonMessage Message { " + resourceDeliveryMessage.getPayloadId() + "}"; + return resource.getClass().getSimpleName() + " { " + resource.getIdElement().getValue() + " }"; } else { return object.toString(); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java index 328895196da..0bcd02c33b2 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java @@ -39,10 +39,10 @@ public class PointcutLatch implements IAnonymousLambdaHook { throw new PointcutLatchException("setExpectedCount() called before previous awaitExpected() completed."); } createLatch(count); + ourLog.info("Expecting {} calls to {} latch", count, name); } private void createLatch(int count) { - ourLog.info("Creating new latch with count {}", count); myFailure = new AtomicReference<>(); myCalledWith = new AtomicReference<>(new ArrayList<>()); myCountdownLatch = new CountDownLatch(count); @@ -76,17 +76,17 @@ public class PointcutLatch implements IAnonymousLambdaHook { throw new AssertionError(error); } } finally { - destroyLatch(); + clear(); } assertEquals("Concurrency error: Latch switched while waiting.", retval, myCalledWith.get()); return retval; } public void expectNothing() { - destroyLatch(); + clear(); } - private void destroyLatch() { + public void clear() { myCountdownLatch = null; } @@ -115,11 +115,8 @@ public class PointcutLatch implements IAnonymousLambdaHook { if (myCalledWith.get() != null) { myCalledWith.get().add(theArgs); } - this.countdown(); - } + ourLog.info("Called {} {} with {}", name, myCountdownLatch, hookParamsToString(theArgs)); - private void countdown() { - ourLog.info("{} counting down {}", name, myCountdownLatch); myCountdownLatch.countDown(); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestHelper.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestHelper.java new file mode 100644 index 00000000000..f9819c16ff6 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestHelper.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Subscription; + +import java.util.concurrent.atomic.AtomicLong; + +public class SubscriptionTestHelper { + + protected static AtomicLong idCounter = new AtomicLong(); + + + public Subscription makeActiveSubscription(String theCriteria, String thePayload, String theEndpoint) { + return makeSubscriptionWithStatus(theCriteria, thePayload, theEndpoint, Subscription.SubscriptionStatus.ACTIVE); + } + + public Subscription makeSubscriptionWithStatus(String theCriteria, String thePayload, String theEndpoint, Subscription.SubscriptionStatus status) { + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(status); + subscription.setCriteria(theCriteria); + IdType id = new IdType("Subscription", nextId()); + subscription.setId(id); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); + channel.setPayload(thePayload); + channel.setEndpoint(theEndpoint); + subscription.setChannel(channel); + return subscription; + } + + public long nextId() { + return idCounter.incrementAndGet(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java index 995b03f1fe3..efbdbad6e0c 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java @@ -25,7 +25,6 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -39,7 +38,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends BaseSubscriptionDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionMatchingSubscriberTest.class); @@ -67,8 +65,6 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base protected static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); protected static List ourContentTypes = Collections.synchronizedList(new ArrayList<>()); private static SubscribableChannel ourSubscribableChannel; - private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); - protected static AtomicLong idCounter = new AtomicLong(); protected PointcutLatch mySubscriptionMatchingPost = new PointcutLatch(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED); protected PointcutLatch mySubscriptionActivatedPost = new PointcutLatch(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED); @@ -101,32 +97,16 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base } protected Subscription sendSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException { - Subscription subscription = returnedActiveSubscription(theCriteria, thePayload, theEndpoint); + Subscription subscription = makeActiveSubscription(theCriteria, thePayload, theEndpoint); mySubscriptionActivatedPost.setExpectedCount(1); Subscription retval = sendResource(subscription); mySubscriptionActivatedPost.awaitExpected(); return retval; } - protected Subscription returnedActiveSubscription(String theCriteria, String thePayload, String theEndpoint) { - Subscription subscription = new Subscription(); - subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); - subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE); - subscription.setCriteria(theCriteria); - IdType id = new IdType("Subscription", idCounter.incrementAndGet()); - subscription.setId(id); - - Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); - channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); - channel.setPayload(thePayload); - channel.setEndpoint(theEndpoint); - subscription.setChannel(channel); - return subscription; - } - protected Observation sendObservation(String code, String system) throws InterruptedException { Observation observation = new Observation(); - IdType id = new IdType("Observation", idCounter.incrementAndGet()); + IdType id = new IdType("Observation", nextId()); observation.setId(id); CodeableConcept codeableConcept = new CodeableConcept(); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java new file mode 100644 index 00000000000..27a3571d3be --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java @@ -0,0 +1,48 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber; +import org.hl7.fhir.dstu3.model.Subscription; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; + +public class StandaloneSubscriptionMessageHandlerTest extends BaseSubscriptionDstu3Test { + + @Autowired + StandaloneSubscriptionMessageHandler myStandaloneSubscriptionMessageHandler; + @Autowired + FhirContext myFhirContext; + @MockBean + SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; + @MockBean + SubscriptionRegistry mySubscriptionRegistry; + + @Test + public void activeSubscriptionIsRegistered() { + Subscription subscription = makeActiveSubscription("testCriteria", "testPayload", "testEndpoint"); + ResourceModifiedMessage message = new ResourceModifiedMessage(myFhirContext, subscription, ResourceModifiedMessage.OperationTypeEnum.CREATE); + ResourceModifiedJsonMessage jsonMessage = new ResourceModifiedJsonMessage(message); + myStandaloneSubscriptionMessageHandler.handleMessage(jsonMessage); + Mockito.verify(mySubscriptionRegistry).registerSubscriptionUnlessAlreadyRegistered(any()); + Mockito.verify(mySubscriptionMatchingSubscriber).matchActiveSubscriptionsAndDeliver(any()); + } + + @Test + public void requestedSubscriptionNotRegistered() { + Subscription subscription = makeSubscriptionWithStatus("testCriteria", "testPayload", "testEndpoint", Subscription.SubscriptionStatus.REQUESTED); + ResourceModifiedMessage message = new ResourceModifiedMessage(myFhirContext, subscription, ResourceModifiedMessage.OperationTypeEnum.CREATE); + ResourceModifiedJsonMessage jsonMessage = new ResourceModifiedJsonMessage(message); + myStandaloneSubscriptionMessageHandler.handleMessage(jsonMessage); + Mockito.verify(mySubscriptionRegistry, never()).registerSubscriptionUnlessAlreadyRegistered(any()); + Mockito.verify(mySubscriptionMatchingSubscriber).matchActiveSubscriptionsAndDeliver(any()); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java index 3d57b5e9011..578e2da944f 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java @@ -12,9 +12,7 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscribableChannelDstu3Test { @Test @@ -28,8 +26,8 @@ public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscriba String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; List subs = new ArrayList<>(); - subs.add(returnedActiveSubscription(criteria1, payload, ourListenerServerBase)); - subs.add(returnedActiveSubscription(criteria2, payload, ourListenerServerBase)); + subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase)); + subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase)); IBundleProvider bundle = new SimpleBundleProvider(new ArrayList<>(subs), "uuid"); initSubscriptionLoader(bundle); @@ -53,8 +51,8 @@ public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscriba String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; List subs = new ArrayList<>(); - subs.add(returnedActiveSubscription(criteria1, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); - subs.add(returnedActiveSubscription(criteria2, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); + subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); + subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); IBundleProvider bundle = new SimpleBundleProvider(new ArrayList<>(subs), "uuid"); initSubscriptionLoader(bundle); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderTest.java index cb85aa724f5..0def5f74047 100755 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderTest.java @@ -40,8 +40,8 @@ public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannel String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; List subs = new ArrayList<>(); - subs.add(returnedActiveSubscription(criteria1, payload, ourListenerServerBase)); - subs.add(returnedActiveSubscription(criteria2, payload, ourListenerServerBase)); + subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase)); + subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase)); IBundleProvider bundle = new SimpleBundleProvider(new ArrayList<>(subs), "uuid"); initSubscriptionLoader(bundle); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java index 2ff76686ceb..b946afddd06 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java @@ -95,7 +95,7 @@ public class SubscriptionCheckingSubscriberTest extends BaseBlockingQueueSubscri ourObservationListener.setExpectedCount(1); Observation observation = new Observation(); - IdType id = new IdType("Observation", idCounter.incrementAndGet()); + IdType id = new IdType("Observation", nextId()); observation.setId(id); // Reference has display only! diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 0a89d730295..5649cbba2b1 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -121,7 +121,7 @@ org.springframework.boot spring-boot-dependencies - ${spring-boot.version} + ${spring_boot_version} pom import diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 153f1fa3d7a..27317a44d4d 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-dependencies - ${spring-boot.version} + ${spring_boot_version} pom import diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 86ecbc36d5f..ab9fb0c48a6 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -30,7 +30,7 @@ org.springframework.boot spring-boot-dependencies - ${spring-boot.version} + ${spring_boot_version} pom import diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 1e3fc36f83a..e7fb691f6b1 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-dependencies - ${spring-boot.version} + ${spring_boot_version} pom import diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java new file mode 100644 index 00000000000..082a737d845 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java @@ -0,0 +1,426 @@ +package ca.uhn.fhir.parser.jsonlike; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.GsonStructure; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.util.TestUtil; + +public class JsonLikeParserDstu3Test { + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu3Test.class); + + /** + * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) + */ + @Test + public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception { + String content = IOUtils.toString(JsonLikeParserDstu3Test.class.getResourceAsStream("/bundle_with_woven_obs.xml")); + + Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, content); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); + ourLog.info(encoded); + + JsonLikeStructure jsonLikeStructure = new GsonStructure(); + jsonLikeStructure.load(new StringReader(encoded)); + + IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); + + Bundle bundle = jsonLikeparser.parseResource(Bundle.class, jsonLikeStructure); + + } + + /** + * Test JSON-Like writer using custom stream writer + * + */ + @Test + public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { + String refVal = "http://my.org/FooBar"; + + Patient fhirPat = new Patient(); + fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); + + IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); + JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); + + jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); + Map jsonLikeMap = jsonLikeWriter.getResultMap(); + + System.out.println("encoded map: " + jsonLikeMap.toString()); + + Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); + Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); + + Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); + Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); + + List extensions = (List)jsonLikeMap.get("extension"); + Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); + Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); + + Map extension = (Map)extensions.get(0); + Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); + Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); + Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + + public static class JsonLikeMapWriter extends JsonLikeWriter { + + private Map target; + + private static class Block { + private BlockType type; + private String name; + private Map object; + private List array; + public Block(BlockType type) { + this.type = type; + } + public BlockType getType() { + return type; + } + public String getName() { + return name; + } + public void setName(String currentName) { + this.name = currentName; + } + public Map getObject() { + return object; + } + public void setObject(Map currentObject) { + this.object = currentObject; + } + public List getArray() { + return array; + } + public void setArray(List currentArray) { + this.array = currentArray; + } + } + private enum BlockType { + NONE, OBJECT, ARRAY + } + private Block currentBlock = new Block(BlockType.NONE); + private Stack blockStack = new Stack(); + + public JsonLikeMapWriter () { + super(); + } + + public Map getResultMap() { + return target; + } + public void setResultMap(Map target) { + this.target = target; + } + + @Override + public JsonLikeWriter init() throws IOException { + if (target != null) { + target.clear(); + } + currentBlock = new Block(BlockType.NONE); + blockStack.clear(); + return this; + } + + @Override + public JsonLikeWriter flush() throws IOException { + if (currentBlock.getType() != BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); + } + return this; + } + + @Override + public void close() { + // nothing to do + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + Map newObject = null; + if (currentBlock.getType() == BlockType.NONE) { + if (null == target) { + // for this test, we don't care about ordering of map elements + // target = new EntryOrderedMap(); + target = new HashMap(); + } + newObject = target; + } else { + // for this test, we don't care about ordering of map elements + // newObject = new EntryOrderedMap(); + newObject = new HashMap(); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setObject(newObject); + return this; + } + + @Override + public JsonLikeWriter beginArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setName(name); + // for this test, we don't care about ordering of map elements + // currentBlock.setObject(new EntryOrderedMap()); + currentBlock.setObject(new HashMap()); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setName(name); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(null); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, null); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.OBJECT) { + ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.ARRAY) { + ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endBlock() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); + } else { + Object toPut = null; + if (currentBlock.getType() == BlockType.ARRAY) { + toPut = currentBlock.getArray(); + } else { + toPut = currentBlock.getObject(); + } + Block parentBlock = blockStack.pop(); + if (parentBlock.getType() == BlockType.OBJECT) { + parentBlock.getObject().put(currentBlock.getName(), toPut); + } else + if (parentBlock.getType() == BlockType.ARRAY) { + parentBlock.getArray().add(toPut); + } + currentBlock = parentBlock; + } + return this; + } + + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java new file mode 100644 index 00000000000..f82b2ce3153 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java @@ -0,0 +1,732 @@ +package ca.uhn.fhir.parser.jsonlike; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Extension; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.parser.json.GsonStructure; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; +import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; +import ca.uhn.fhir.parser.view.ExtPatient; +import ca.uhn.fhir.util.TestUtil; + +public class JsonLikeParserTest { + private static FhirContext ourCtx = FhirContext.forR4(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserTest.class); + + /** + * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) + */ + @Test + public void testJsonLikeParseAndEncodeResourceFromXmlToJson() throws Exception { + String content = IOUtils.toString(JsonLikeParserTest.class.getResourceAsStream("/extension-on-line.txt")); + + IBaseResource parsed = ourCtx.newJsonParser().parseResource(content); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); + ourLog.info(encoded); + + JsonLikeStructure jsonLikeStructure = new GsonStructure(); + jsonLikeStructure.load(new StringReader(encoded)); + + IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); + + IBaseResource resource = jsonLikeparser.parseResource(jsonLikeStructure); + Assert.assertEquals("reparsed resource classes not equal", parsed.getClass().getName(), resource.getClass().getName()); + } + + /** + * Test JSON-Like writer using custom stream writer + * + */ + @Test + public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { + String refVal = "http://my.org/FooBar"; + + Patient fhirPat = new Patient(); + fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); + + IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); + JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); + + jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); + Map jsonLikeMap = jsonLikeWriter.getResultMap(); + + System.out.println("encoded map: " + jsonLikeMap.toString()); + + Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); + Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); + + Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); + Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); + + List extensions = (List)jsonLikeMap.get("extension"); + Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); + Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); + + Map extension = (Map)extensions.get(0); + Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); + Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); + Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); + + } + + /** + * Repeat the "View" tests with custom JSON-Like structure + */ + @Test + public void testViewJson() throws Exception { + + ExtPatient src = new ExtPatient(); + src.addIdentifier().setSystem("urn:sys").setValue("id1"); + src.addIdentifier().setSystem("urn:sys").setValue("id2"); + src.getExt().setValue(100); + src.getModExt().setValue(200); + + IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); + JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); + jsonLikeParser.encodeResourceToJsonLikeWriter(src, jsonLikeWriter); + Map jsonLikeMap = jsonLikeWriter.getResultMap(); + + + ourLog.info("encoded: "+jsonLikeMap); + + JsonLikeStructure jsonStructure = new JsonLikeMapStructure(jsonLikeMap); + IJsonLikeParser parser = (IJsonLikeParser)ourCtx.newJsonParser(); + Patient nonExt = parser.parseResource(Patient.class, jsonStructure); + + Assert.assertEquals(Patient.class, nonExt.getClass()); + Assert.assertEquals("urn:sys", nonExt.getIdentifier().get(0).getSystem()); + Assert.assertEquals("id1", nonExt.getIdentifier().get(0).getValue()); + Assert.assertEquals("urn:sys", nonExt.getIdentifier().get(1).getSystem()); + Assert.assertEquals("id2", nonExt.getIdentifier().get(1).getValue()); + + List ext = nonExt.getExtensionsByUrl("urn:ext"); + Assert.assertEquals(1, ext.size()); + Assert.assertEquals("urn:ext", ext.get(0).getUrl()); + Assert.assertEquals(IntegerType.class, ext.get(0).getValueAsPrimitive().getClass()); + Assert.assertEquals("100", ext.get(0).getValueAsPrimitive().getValueAsString()); + + List modExt = nonExt.getExtensionsByUrl("urn:modExt"); + Assert.assertEquals(1, modExt.size()); + Assert.assertEquals("urn:modExt", modExt.get(0).getUrl()); + Assert.assertEquals(IntegerType.class, modExt.get(0).getValueAsPrimitive().getClass()); + Assert.assertEquals("200", modExt.get(0).getValueAsPrimitive().getValueAsString()); + + ExtPatient va = ourCtx.newViewGenerator().newView(nonExt, ExtPatient.class); + Assert.assertEquals("urn:sys", va.getIdentifier().get(0).getSystem()); + Assert.assertEquals("id1", va.getIdentifier().get(0).getValue()); + Assert.assertEquals("urn:sys", va.getIdentifier().get(1).getSystem()); + Assert.assertEquals("id2", va.getIdentifier().get(1).getValue()); + Assert.assertEquals(100, va.getExt().getValue().intValue()); + Assert.assertEquals(200, va.getModExt().getValue().intValue()); + + Assert.assertEquals(0, va.getExtension().size()); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + + public static class JsonLikeMapWriter extends JsonLikeWriter { + + private Map target; + + private static class Block { + private BlockType type; + private String name; + private Map object; + private List array; + public Block(BlockType type) { + this.type = type; + } + public BlockType getType() { + return type; + } + public String getName() { + return name; + } + public void setName(String currentName) { + this.name = currentName; + } + public Map getObject() { + return object; + } + public void setObject(Map currentObject) { + this.object = currentObject; + } + public List getArray() { + return array; + } + public void setArray(List currentArray) { + this.array = currentArray; + } + } + private enum BlockType { + NONE, OBJECT, ARRAY + } + private Block currentBlock = new Block(BlockType.NONE); + private Stack blockStack = new Stack(); + + public JsonLikeMapWriter () { + super(); + } + + public Map getResultMap() { + return target; + } + public void setResultMap(Map target) { + this.target = target; + } + + @Override + public JsonLikeWriter init() throws IOException { + if (target != null) { + target.clear(); + } + currentBlock = new Block(BlockType.NONE); + blockStack.clear(); + return this; + } + + @Override + public JsonLikeWriter flush() throws IOException { + if (currentBlock.getType() != BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); + } + return this; + } + + @Override + public void close() { + // nothing to do + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + Map newObject = null; + if (currentBlock.getType() == BlockType.NONE) { + if (null == target) { + // for this test, we don't care about ordering of map elements + // target = new EntryOrderedMap(); + target = new HashMap(); + } + newObject = target; + } else { + // for this test, we don't care about ordering of map elements + // newObject = new EntryOrderedMap(); + newObject = new HashMap(); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setObject(newObject); + return this; + } + + @Override + public JsonLikeWriter beginArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setName(name); + // for this test, we don't care about ordering of map elements + // currentBlock.setObject(new EntryOrderedMap()); + currentBlock.setObject(new HashMap()); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setName(name); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(null); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, null); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.OBJECT) { + ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.ARRAY) { + ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endBlock() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); + } else { + Object toPut = null; + if (currentBlock.getType() == BlockType.ARRAY) { + toPut = currentBlock.getArray(); + } else { + toPut = currentBlock.getObject(); + } + Block parentBlock = blockStack.pop(); + if (parentBlock.getType() == BlockType.OBJECT) { + parentBlock.getObject().put(currentBlock.getName(), toPut); + } else + if (parentBlock.getType() == BlockType.ARRAY) { + parentBlock.getArray().add(toPut); + } + currentBlock = parentBlock; + } + return this; + } + + } + + public static class JsonLikeMapStructure implements JsonLikeStructure { + + private Map nativeObject; + private JsonLikeObject jsonLikeObject = null; + private JsonLikeMapWriter jsonLikeWriter = null; + + public JsonLikeMapStructure() { + super(); + } + + public JsonLikeMapStructure (Map json) { + super(); + setNativeObject(json); + } + + public void setNativeObject (Map json) { + this.nativeObject = json; + } + + @Override + public JsonLikeStructure getInstance() { + return new JsonLikeMapStructure(); + } + + @Override + public JsonLikeWriter getJsonLikeWriter (Writer ignored) { + return getJsonLikeWriter(); + } + + @Override + public JsonLikeWriter getJsonLikeWriter () { + if (null == jsonLikeWriter) { + jsonLikeWriter = new JsonLikeMapWriter(); + } + return jsonLikeWriter; + } + + @Override + public void load(Reader reader) throws DataFormatException { + this.load(reader, true); + } + + @Override + public void load(Reader theReader, boolean allowArray) throws DataFormatException { + throw new DataFormatException("JSON structure loading is not supported for native Java Map structures"); + } + + @Override + public JsonLikeObject getRootObject() { + if (null == jsonLikeObject) { + jsonLikeObject = new JsonMapObject(nativeObject); + } + return jsonLikeObject; + } + + @Override + public JsonLikeArray getRootArray() throws DataFormatException { + throw new DataFormatException("JSON document must be an object not an array for native Java Map structures"); + } + + private class JsonMapObject extends JsonLikeObject { + private Map nativeObject; + private Map jsonLikeMap = new LinkedHashMap(); + + public JsonMapObject (Map json) { + this.nativeObject = json; + } + + @Override + public Object getValue() { + return nativeObject; + } + + @Override + public Set keySet() { + return nativeObject.keySet(); + } + + @Override + public JsonLikeValue get(String key) { + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + Object child = nativeObject.get(key); + if (child != null) { + result = new JsonMapValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private class JsonMapArray extends JsonLikeArray { + private List nativeArray; + private Map jsonLikeMap = new LinkedHashMap(); + + public JsonMapArray (List json) { + this.nativeArray = json; + } + + @Override + public Object getValue() { + return nativeArray; + } + + @Override + public int size() { + return nativeArray.size(); + } + + @Override + public JsonLikeValue get(int index) { + Integer key = Integer.valueOf(index); + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + Object child = nativeArray.get(index); + if (child != null) { + result = new JsonMapValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private class JsonMapValue extends JsonLikeValue { + private Object nativeValue; + private JsonLikeObject jsonLikeObject = null; + private JsonLikeArray jsonLikeArray = null; + + public JsonMapValue (Object json) { + this.nativeValue = json; + } + + @Override + public Object getValue() { + return nativeValue; + } + + @Override + public ValueType getJsonType() { + if (isNull()) { + return ValueType.NULL; + } + if (isObject()) { + return ValueType.OBJECT; + } + if (isArray()) { + return ValueType.ARRAY; + } + return ValueType.SCALAR; + } + + @Override + public ScalarType getDataType() { + if (isString()) { + return ScalarType.STRING; + } + if (isNumber()) { + return ScalarType.NUMBER; + } + if (isBoolean()) { + return ScalarType.BOOLEAN; + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public JsonLikeArray getAsArray() { + if (nativeValue != null && isArray()) { + if (null == jsonLikeArray) { + jsonLikeArray = new JsonMapArray((List)nativeValue); + } + } + return jsonLikeArray; + } + + @SuppressWarnings("unchecked") + @Override + public JsonLikeObject getAsObject() { + if (nativeValue != null && isObject()) { + if (null == jsonLikeObject) { + jsonLikeObject = new JsonMapObject((Map)nativeValue); + } + } + return jsonLikeObject; + } + + @Override + public String getAsString() { + String result = null; + if (nativeValue != null) { + result = nativeValue.toString(); + } + return result; + } + + @Override + public boolean getAsBoolean() { + if (nativeValue != null && isBoolean()) { + return ((Boolean)nativeValue).booleanValue(); + } + return super.getAsBoolean(); + } + + public boolean isObject () { + return (nativeValue != null) + && ( (nativeValue instanceof Map) || Map.class.isAssignableFrom(nativeValue.getClass()) ); + } + + public boolean isArray () { + return (nativeValue != null) + && ( (nativeValue instanceof List) || List.class.isAssignableFrom(nativeValue.getClass())); + } + + public boolean isString () { + return (nativeValue != null) + && ( (nativeValue instanceof String) || String.class.isAssignableFrom(nativeValue.getClass())); + } + + public boolean isNumber () { + return (nativeValue != null) + && ( (nativeValue instanceof Number) || Number.class.isAssignableFrom(nativeValue.getClass()) ); + } + + public boolean isBoolean () { + return (nativeValue != null) + && ( (nativeValue instanceof Boolean) || Boolean.class.isAssignableFrom(nativeValue.getClass()) ); + } + + public boolean isNull () { + return (null == nativeValue); + } + } + } +} diff --git a/pom.xml b/pom.xml index 48c86ac4757..384e27cf290 100755 --- a/pom.xml +++ b/pom.xml @@ -563,7 +563,7 @@ 1.7.25 5.1.3.RELEASE 2.1.3.RELEASE - 2.1.1.RELEASE + 2.1.1.RELEASE 1.2.2.RELEASE 3.1.4 @@ -1256,6 +1256,11 @@ spring-test ${spring_version} + + org.springframework.boot + spring-boot-starter-test + ${spring_boot_version} + org.springframework spring-tx @@ -1334,7 +1339,7 @@ org.springframework.boot spring-boot-maven-plugin - ${spring-boot.version} + ${spring_boot_version} org.sonatype.plugins