A következő címkéjű bejegyzések mutatása: REST. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: REST. Összes bejegyzés megjelenítése

2017. október 24., kedd

Implementing inheritance for JSON objects


Our App developers wanted to send slightly different JSON elements in a single list, in order to make the App side implementation far less complicated. In order to make it possible to, I decided to implement inheritance hierarchy for the JSON input and the related java DTO classes.

The other possibility would be to have a JSON object with union of the fields from all Java classes and using @JsonIgnoreProperties(ignoreUnknown = true) for them. In this way you would be able to parse only the relevant field into the given Java class.

Advantages of using hierarchy in JSON related classes:

  • more object oriented implementation
  • you can declare different type of objects in a single JSON list, so far they have the same Java super class
  • automatic REST input validation of the Spring framework still works for the classes. Yo do not need to define checking logic with mixed fields and complicated conditions. 
  • easy to add new types in the future, without effecting the existing ones 

Disadvantages:
  • you need to consider if storing different elements in a single list is a god idea at all
  • complicated structure
  • error prone type definition. It is not possible to define enumeration or any other typed value for the name property of @Type (see below)
  • unit testing of the hierarchy is more complicated
  • difficult to change break the hierarchy structure in case modification is needed later on

I created a common super class for the root of the inheritance and defined the field "actionType" for discriminator.  


@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "actionType")
@JsonSubTypes({
 @Type(value = TriggeredNotificationActionDto.class, name = "NOTIFICATION"),
 @Type(value = TriggeredDeviceActionDto.class, name = "DEVICE")
})
public static class TriggeredActionDto implements Serializable {
 private String actionType;
}

I defined the actual sub-classes (using annotation of Lombok project for getter and setter generation). I also defined validators for the fields. They are used by the Spring framework when the objects are acting as input parameter of a REST call.

@Data
@EqualsAndHashCode(callSuper = true)
public static class TriggeredNotificationActionDto extends TriggeredActionDto {
 public enum NotificationActionType {
  WEBSOCKET, PUSH;
 }

 @JsonProperty("notificationType")
 @NotNull(message = "notificationType must not be blank!")
 private NotificationActionType notificationType;
}



@Data
@EqualsAndHashCode(callSuper = true)
public static class TriggeredDeviceActionDto extends TriggeredActionDto {
 @ApiModelProperty(example = "swagger001")
 @JsonProperty("deviceId")
 @NotNull(message = "Device id must not be blank!")
 private String deviceId;

 @ApiModelProperty(example = "1")
 @JsonProperty("channelId")
 @NotNull(message = "Channel id must not be blank!")
 @Range(min = 1, message = "Channel id must be a positive number")
 private int channelId;

 @ApiModelProperty(example = "1")
 @JsonProperty("value")
 @NotNull(message = "Value must not be blank!")
 private int value;

...
}


In the class, containing the TriggeredActionDto elements, I marked the list as @Valid, in order to force validation of the each elements of the list.


@JsonProperty("tasks")
@Valid
private List<TriggeredActionDto> tasks;



2017. október 5., csütörtök

Custom validator for REST parameter in Spring Boot


Using Spring boot applications, it is very easy to let the Spring framework to do the validation of the REST input parameter. You only need to add the corresponding validation annotations to the properties of the parameter class, and mark the parameter with @Valid annotation in the method header.


@RequestMapping(value = "/register", method = { RequestMethod.POST })
public RestResponse register(HttpServletRequest httpServletRequest, 
       @Valid @RequestBody PushRegistrationRequest registrationRequest) {



In case of your REST input object is validated with the @Valid annotation, most of the time, it is enough to  use default validations, like @NotNull, @Pattern, @Size, etc. Sometimes though you need to do more complex validations, considering multiple properties. To achieve this, you need to write your own annotation, and a corresponding validation class.

The annotation looks like this:


@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { HasCorrectAppIdValidator.class })
public @interface HasCorrectAppId {
 String message() default "App id is incorrect";

 Class<?>[] groups() default {};

 Class<? extends Payload>[] payload() default {};
}

  As you can see above, in the validatedBy attribute, you can define a validation class. the validator class needs to implement the javax.validation.ConstraintValidator interface.


import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class HasCorrectAppIdValidator implements ConstraintValidator<HasCorrectAppId, PushRegistrationRequest> {

 @Override
 public void initialize(HasCorrectAppId constraintAnnotation) {
  // do nothing
 }

 @Override
 public boolean isValid(PushRegistrationRequest value, ConstraintValidatorContext context) {
  if (IOS.equals(value.getOs()) && value.getAppId().length() != 64) {
   String errorMessage = "length must be 64 for " + ClientOperationSystem.IOS;
   createErrorInContext(context, errorMessage);
   return false;

  } else if (ANDROID.equals(value.getOs()) && value.getAppId().length() < 65) {
   String errorMessage = "length must be greater than 65 for " + ClientOperationSystem.ANDROID;
   createErrorInContext(context, errorMessage);
   return false;
  }
  return true;
 }

 private void createErrorInContext(ConstraintValidatorContext context, String errorMessage) {
  context.disableDefaultConstraintViolation();
  context.buildConstraintViolationWithTemplate(errorMessage)
    .addPropertyNode("appId")
    .addConstraintViolation();
 }
}


That's it. In order to use tell to the framework to use the validator is, to annotate the parameter class with the new annotation.




@HasCorrectAppId
public class PushRegistrationRequest implements Serializable {
    ...
}


2016. november 17., csütörtök

Using JIRA REST API via Restclient.



While implementing JIRA plugins, I recently had to use JIRA REST API to create and modify issues.

As I did not want to install any desktop application, I decided to use the RESTclient plug-in for Firefox.

It provides a very convenient way to deal with REST requests, and makes it possible to try out your own REST functions fast.

I however faced some problems during setting up the client, so I list here, how to do it properly, in order to avoid wasting my time at the next ocassion.
  • Install the RESTclient add-on for Firefox, from the Firefox application store
  • Start the plugin
  • Set up the request:
  • Method: POST
  • URL: http://localhost:2990/jira/rest/api/2/issue
  • Define authentication by adding a new authentication element to the request. Select basic authentication, and set username and password
  • Define content type by adding a new header element: Content-Type:application/json
  • Define user agent: User-Agent:adminIt is necessary, for avoid JIRA error message: "403 Forbidden". If it is not defined, you get error from the REST API, and following log entry is shown in JIRA log:
    "XSRF checks failed for request"














  • Define message body

{
    "fields": {
       "project":
       { 
          "key": "TEST"
       },
       "summary": "REST ye merry gentleme.",
       "description": "Creating of an issue using project keys and issue type names using the REST API",
       "issuetype": {
          "name": "Bug"
       }
   }
}
  • Send the REST command.

For REST commands visit the JIRA documentation.


2016. szeptember 28., szerda

Using Application Link in Atlassian plugins



When implementing complex plug-ins for the Atlassian suite, it might be needed to request information from another Atlassian application. You can see examples for it, when JIRA shows Stash commits, of Confluence pages.

As all Atlassian applications has a REST API, it is possible to make JSON calls directly. Using Application Links for communication between Atlassian products however has following advantages

  • It uses central authentication of the Atlassian applications. You can take advantage of the centralized CROWD user management, and don't need to store user and password in some configuration of your plug-in. You also don't need to set authentication data in your code.
  • You use components of the common com.atlassian.applinks.api package, and it is part of the core features of all Atlassian applications. All needed components are included in com.atlassian.applinks maven group, which is automatically included by the application. Therefore you do not need to add any additional maven dependencies to your project.
  • As application liking is supported by all Atlassian products, you can reach all of them with a single programming API from your add-on.
Disadvantage of the solution is, that you are working with pure JSON responses, so in order to map them into the Java world, you need a mapping framework (like Jackson or Gson), and most probably implement your own classes holding the returned information.

If you want to use a single application, with a more object oriented way, you can consider using product specific API.s like JiraRestClient from the com.atlassian.jira.rest.client.api package.  

Configuring Application link

In order to use functionalities of JIRA from my Confluence plug-in, I have created an Application link between the two servers. In local development environment I had to configure the link to use the "Trusted application" authentication type, in order to get it worked.

I made the following steps to create the application link

  1. Start the JIRA and the Confluence server locally, using the atlassian-run command in the root directory of the plug-ins.
  2. Log in as admin to both applications
  3. In JIRA navigate to Administration/Applications/Application links
  4. Create a new application link following the wizard. The process includes to go to the Confluence administration page and set the inverse link as well.
  5. After the application links are visible on both of the servers, set the authentication for incoming and outgoing link to "OAuth (impersonation)" everywhere.
    Doing so, you configures trusted application connection, and you do not need to bother with authentication in the plug-in code.

Implementing usage of Application link

I have implemented an example to show, how to get a list of all JIRA fields in Confluence using the application link to JIRA.

The call via application link uses the REST API URL providing field information: http://javadeveloper:2990/jira/rest/api/2/field

The result from JIRA looks like this:


[
{"id":"issuetype","name":"Issue Type","custom":false,"orderable":true,
 "navigable":true,"searchable":true,"clauseNames":["issuetype","type"],
 "schema":{"type":"issuetype","system":"issuetype"}},
{"id":"components","name":"Component/s","custom":false,"orderable":true,
  "navigable":true,"searchable":true,"clauseNames":["component"],
  "schema":{"type":"array","items":"component","system":"components"}}, ...

At the first step, I create an ApplicationLinkRequest pointing to the given URL. 


final static int APP_LINK_TIME_OUT = 60000;

// Logger instance
private static final Logger log = LoggerFactory.getLogger(JiraServiceCaller.class);

@Autowired
private ApplicationLinkService appLinkService;

/**
 * Creates ApplicationLinkRequest for calling JIRA REST service, based on the restServiceUrl parameter.
 * 
 * @param restServiceUrl
 * @return
 */
protected ApplicationLinkRequest createApplicationLinkRequest(String restServiceUrl) {
 MethodType methodType = Request.MethodType.POST;
 ApplicationLinkRequest aplrq = createApplicationLinkRequest(restServiceUrl, methodType);
 return aplrq;
}

private ApplicationLinkRequest createApplicationLinkRequest(String restServiceUrl, MethodType methodType) {
 ApplicationLink appLink = appLinkService.getPrimaryApplicationLink(JiraApplicationType.class);
 if (appLink == null) {
  log.info("Failed to handle REST request. CredentialsRequiredException occured.");
  throw new JiraConnectionException("Unable to get application link of type 'JiraApplication'");
 }

 ApplicationLinkRequestFactory factory = appLink.createAuthenticatedRequestFactory();
  ApplicationLinkRequest aplrq = null;
 try {
  aplrq = factory.createRequest(methodType, appLink.getRpcUrl() + restServiceUrl);
  aplrq.setSoTimeout(APP_LINK_TIME_OUT);

 } catch (CredentialsRequiredException e) {
  log.warn("Error while creating ApplicationLinkRequest", e);
  throw new JiraConnectionException("Unable to connect JIRA via application link. Error message: '" + e.getMessage() + "'");
 }
 return aplrq;
}


Than I execute the configured ApplicationLinkRequest to get the result of the REST API call.


private static final String REST_JIRA_RETRIEVE_FIELDS = "/rest/api/2/field";
 
/**
 * Retrieves a list containing name of all existing JIRA fields
 */
@Override
public List<String> retrieveFieldNames() {
 List<String> result = new ArrayList<String>();

 try {
  ApplicationLinkRequest alr = createApplicationLinkGetRequest(REST_JIRA_RETRIEVE_FIELDS);
  String jiraResponse = alr.execute();
  if (StringUtils.isNotBlank(jiraResponse)) {
   ObjectMapper mapper = new ObjectMapper();
   JsonJiraField[] myObjects = mapper.readValue(jiraResponse, JsonJiraField[].class);
   for (int i = 0; i < myObjects.length; i++) {
    JsonJiraField jsonJiraField = myObjects[i];
    result.add(jsonJiraField.getName());
   }
  }
 } catch (IOException | ResponseException e) {
  log.warn("Failed to handle REST request. IOException occured.", e.getMessage());
 }
 return result;
}


For mapping between the JSON sting and the Java code, I implemented a simple value object, holding the JIRA field info, and used the ObjectMapper from Jackson to fill it up with data.     

Add parameters to the REST call

The ApplicationLinkRequest makes it possible to add parameters to your REST call as key - value pairs, like this:



alr.addRequestParameters("projectKey", projectKey);