Java client for Crossing Minds B2B API
Building the API client library requires:
- Java 11+ (for development/unit testing it is recommended to use AdoptJDK free version)
- Maven 3.6+ (download here then install and see also how to use)
To package the API client, execute:
mvn clean packageTo install the API client library to your local Maven repository, simply execute:
mvn clean installTo run tests, execute the command:
mvn clean testThe API is built as a Java / Maven project.
For JSON mapping we use the Jackson standard (Java JSON library) (see information here) which is a powerful set of data processing tools for Java (and the JVM platform) so it is well known and used.
To comply with the Jackson standard it is necessary to generate model classes (POJOs) to map each entity of the system with its attributes, but with the ability to map extra attributes through an additional attribute for this purpose.
// Database class to map Database entity
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder({ "id", "organization_id", "name", "description", "item_id_type", "user_id_type", "counters" })
public class Database extends Base {
@JsonProperty("id")
private String id;
@JsonProperty("organization_id")
private String organizationId;
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("item_id_type")
private String itemIdType;
@JsonProperty("user_id_type")
private String userIdType;
@JsonProperty("counters")
private Counters counters;
private static final long serialVersionUID = 1261106258660845138L;
// Note that Database class extends of Base class containing the feature to map any XMinds error and additional properties
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder({ "error" })
public class Base implements Serializable {
@JsonUnwrapped
@JsonProperty("error")
@JsonIgnore
private BaseError error;
@Builder.Default
@JsonIgnore
protected transient Map<String, Object> additionalProperties = new HashMap<>();
private static final long serialVersionUID = 6984020505060503600L;Also @Lombok is used to simplify the POJO construction.
// JSON to Java object
public T jsonToObject(String jsonContent, Class<T> clazz) {
try {
return this.readValue(jsonContent, clazz);
} catch (IOException ioe) {
throw new CompletionException(ioe);
}
}
// Java object to JSON string
public String objectToJson(Object obj) {
try {
return this.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new CompletionException(e);
}
}To ensure the use of an automatic refresh token, the Decorator design pattern is implemented (through reflection) adding the ability to obtain a new token from the refresh token stored in memory.
@LoginRequired
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(xmindClient, args);
} catch (InvocationTargetException e) {
try {
throw e.getTargetException();
} catch (JwtTokenExpiredException ex) {
if (!this.hasLoginRequired(method))
throw ex;
xmindClient.loginRefreshToken();
return method.invoke(xmindClient, args);
}
}
}All methods annotated with @LoginRequired are intercepted and then invoked through this flow to ensure authentication using the refresh token.
// Building a default client
XMindClient client = new XmindBuilder().build();
client.loginService(aServiceAccount);
// It is possible to provide some optional parameters
XMindClient client = new XmindBuilder()
.withHost("https://staging-api.crossingminds.com/") // Example for staging host
.withServiceAccount(aServiceAccount) // Will do the login service implicitly
.withUserAgent("USER_AGENT") // Used to keep track of requests (example: "Shopify/MyOwnStoreName")
.withHttpClient(mockHttpClient) // For testing purposes only
.build();// Example of a CustomXmindsClient interface
public interface CustomXmindsClient extends XMindClient {
MyObject myCustomMethod(String id) throws XMindException;
}
// Example of a CustomXmindsClient implementation
public class CustomXmindsClientImpl extends XMindClientImpl implements CustomXmindsClient {
private final String ENDPOINT_RESOURCES = "resource/%s/";
public CustomXmindsClientImpl(HttpClient httpClient, String host, ServiceAccount serviceAccount, String externalUserAgent) throws XMindException {
super(httpClient, host, serviceAccount, externalUserAgent);
}
@LoginRequired
public MyObject myCustomMethod(String id) throws XMindException {
var uri = String.format(ENDPOINT_RESOURCES, id);
return super.request.get(uri, MyObject.class);
}
}
// Building instances
var builder = new XmindBuilder();
// Example of how to create a default instance of the custom client
XMindClient client = (CustomXmindsClient) builder.build(CustomXmindsClient.class, CustomXmindsClientImpl.class);
// Example of creating an instance with custom client parameters (optional)
XMindClient client = (CustomXmindsClient) builder
.withHost("https://staging-api.crossingminds.com/") // Example for staging host
.withServiceAccount(aServiceAccount) // Will do the login service implicitly
.withUserAgent("USER_AGENT") // Used to keep track of requests (example: "Shopify/MyOwnStoreName")
.withHttpClient(mockHttpClient) // For testing purposes only
.build(CustomXmindsClient.class, CustomXmindsClientImpl.class);