Skip to end of banner
Go to start of banner

OpenAPI Specs

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

On this page:

OpenAPI specs provided by the Cloud SPI

The CMJ Cloud SPI provides all API definitions via OpenAPI specs. This will allow you to use many available tools to generate client and server code in your chosen language and validate and test implementations.

We currently provide OpenAPI specs for:

Backward compatibility

We’re keeping the APIs between minor versions backward compatible. Breaking changes (if needed) will be introduced in major versions, and apps will be able to choose when to migrate to a new major version (see apiVersion attribute in SPI Descriptor JSON).

Working with enumeration types

Some of the specs contain enumeration types (enums) - for example, ObjectType in the Operations API. Values may be added to these enums in the future, which will be considered a backward-compatible change.

However, most serialization frameworks in strongly typed languages will require some form of custom deserialization to handle this. Here’s an example of how to achieve it with Jackson:

package com.botronsoft.cmj.cloud.spi.example;

import java.io.IOException;

import com.botronsoft.cmj.cloud.spi.operation.models.ObjectMapping;
import com.botronsoft.cmj.cloud.spi.operation.models.ObjectType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/**
 * A custom Jackson StdDeserializer for ObjectMapping which deserializes ObjectMappings as null in case it receives an unsupported
 * ObjectType.
 */
public final class ObjectMappingDeserializer extends StdDeserializer<ObjectMapping> {

	public ObjectMappingDeserializer() {
		super(ObjectMapping.class);
	}

	public ObjectMapping deserialize(JsonParser parser, DeserializationContext context) throws IOException {
		TreeNode treeNode = parser.getCodec().readTree(parser);
		JsonNode node = (JsonNode) treeNode;
		String sourceId = node.get("sourceId").asText();
		String targetId = node.get("targetId").asText();
		String typeString = node.get("type").asText();

		ObjectMapping result;
		try {
			ObjectType type = ObjectType.valueOf(typeString);
			result = new ObjectMapping(type, sourceId, targetId);
		} catch (IllegalArgumentException ex) {
			result = null;
		}

		return result;
	}
}
package com.botronsoft.cmj.cloud.spi.example;

import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;

/**
 * A custom Jackson CollectionDeserializer which filters out null values from a collection.
 */
public class NonNullItemsCollectionDeserializer extends CollectionDeserializer {

	public NonNullItemsCollectionDeserializer(CollectionDeserializer deserializer) {
		super(deserializer);
	}

	@Override
	public Collection<Object> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
		Collection<Object> objectCollection = super.deserialize(parser, context);
		return objectCollection.parallelStream().filter(Objects::nonNull).collect(Collectors.toList());
	}

	@Override
	public CollectionDeserializer createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
		return new NonNullItemsCollectionDeserializer(super.createContextual(context, property));
	}
}
package com.botronsoft.cmj.cloud.spi.example;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;

public class NonNullCollectionModule extends SimpleModule {

	@Override
	public void setupModule(SetupContext context) {
		super.setupModule(context);
		context.addBeanDeserializerModifier(new CustomBeanDeserializerModifier());
	}

	class CustomBeanDeserializerModifier extends BeanDeserializerModifier {

		@Override
		public JsonDeserializer<?> modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc,
				JsonDeserializer<?> deserializer) {
			if (deserializer instanceof CollectionDeserializer) {
				return new NonNullItemsCollectionDeserializer((CollectionDeserializer) deserializer);
			}

			return super.modifyCollectionDeserializer(config, type, beanDesc, deserializer);
		}
	}
}
package com.botronsoft.cmj.cloud.spi.example;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;

import com.botronsoft.cmj.cloud.spi.operation.models.ObjectMapping;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;

public class TestCustomDeserializerUsage {

	@Test
	public void usage() throws JsonProcessingException {
		SimpleModule objectMappingModule = new SimpleModule();
		objectMappingModule.addDeserializer(ObjectMapping.class, new ObjectMappingDeserializer());
		ObjectMapper objectMapper = new ObjectMapper().registerModules(objectMappingModule, new NonNullCollectionModule());

		String jsonArray = "[\n" +
				"  {\n" +
				"    \"type\": \"ISSUE_TYPE\",\n" +
				"    \"sourceId\": \"10000\",\n" +
				"    \"targetId\": \"10001\"\n" +
				"  },\n" +
				"  {\n" +
				"    \"type\": \"UNSUPPORTED\",\n" +
				"    \"sourceId\": \"10000\",\n" +
				"    \"targetId\": \"10001\"\n" +
				"  }\n" +
				"]\n";

		CollectionType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, ObjectMapping.class);
		List<ObjectMapping> objectMappings = objectMapper.readValue(jsonArray, javaType);
		assertThat(objectMappings.size()).isEqualTo(1); // Mapping with type 'UNSUPPORTED' is skipped
		assertThat(objectMappings.get(0).getType()).isEqualTo(ObjectType.ISSUE_TYPE);
	}
}
  • No labels