http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SecurityQuestionService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SecurityQuestionService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SecurityQuestionService.java
index b9df752..3eb45a4 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SecurityQuestionService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SecurityQuestionService.java
@@ -40,7 +40,6 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.SecurityQuestionTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
@@ -60,7 +59,7 @@ public interface SecurityQuestionService extends JAXRSService 
{
      * @return list of all security questions
      */
     @GET
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     List<SecurityQuestionTO> list();
 
     /**
@@ -71,7 +70,7 @@ public interface SecurityQuestionService extends JAXRSService 
{
      */
     @GET
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     SecurityQuestionTO read(@NotNull @PathParam("key") String key);
 
     /**
@@ -90,8 +89,8 @@ public interface SecurityQuestionService extends JAXRSService 
{
                         @Schema(type = "string"),
                         description = "URL of the entity created") }))
     @POST
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response create(@NotNull SecurityQuestionTO securityQuestionTO);
 
     /**
@@ -105,8 +104,8 @@ public interface SecurityQuestionService extends 
JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @PUT
     @Path("{key}")
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void update(@NotNull SecurityQuestionTO securityQuestionTO);
 
     /**
@@ -118,7 +117,7 @@ public interface SecurityQuestionService extends 
JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @DELETE
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("key") String key);
 
     /**
@@ -129,7 +128,7 @@ public interface SecurityQuestionService extends 
JAXRSService {
      */
     @GET
     @Path("byUser/{username}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     SecurityQuestionTO readByUser(@NotNull @PathParam("username") String 
username);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
index c5e2a67..26332e8 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
@@ -18,11 +18,19 @@
  */
 package org.apache.syncope.common.rest.api.service;
 
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import java.io.InputStream;
 import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -30,14 +38,16 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
-import org.apache.syncope.common.lib.SyncopeConstants;
+import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.info.NumbersInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 
 /**
  * General info about this Apache Syncope deployment.
@@ -58,7 +68,7 @@ public interface SyncopeService extends JAXRSService {
      */
     @GET
     @Path("/platform")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     PlatformInfo platform();
 
     /**
@@ -68,20 +78,72 @@ public interface SyncopeService extends JAXRSService {
      */
     @GET
     @Path("/system")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     SystemInfo system();
 
-    /** *
+    /**
      * Provides some numbers about the managed entities (users, groups, any 
objects...).
      *
      * @return some numbers about the managed entities (users, groups, any 
objects...)
      */
     @GET
     @Path("/numbers")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     NumbersInfo numbers();
 
     /**
+     * Requests for batch execution.
+     *
+     * @param input batch request
+     * @return batch results returned as Response entity, in case no 'Prefer: 
respond-async' was specified
+     */
+    @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
+            description = "Allows client to specify a preference to process 
the batch request asynchronously",
+            allowEmptyValue = true, schema =
+            @Schema(defaultValue = "", allowableValues = { "respond-async" }))
+    @ApiResponses({
+        @ApiResponse(responseCode = "200",
+                description = "Batch request processed, results returned as 
Response entity, "
+                + "in case no 'Prefer: respond-async' was specified"),
+        @ApiResponse(responseCode = "202",
+                description = "Batch accepted for asynchronous processing, "
+                + "in case 'Prefer: respond-async' was specified", headers = {
+                    @Header(name = HttpHeaders.LOCATION, schema =
+                            @Schema(type = "string"),
+                            description = "URL to poll in order to get the 
results of the requested batch processing"),
+                    @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                            @Schema(type = "string"),
+                            description = "Allows the server to inform the "
+                            + "client about the fact that a specified 
preference was applied") }) })
+    @POST
+    @Path("/batch")
+    @Consumes(RESTHeaders.MULTIPART_MIXED)
+    @Produces(RESTHeaders.MULTIPART_MIXED)
+    Response batch(InputStream input);
+
+    /**
+     * Gets batch results, in case asynchronous was requested.
+     *
+     * @return batch results returned as Response entity
+     */
+    @GET
+    @ApiResponses({
+        @ApiResponse(responseCode = "200",
+                description = "Batch results available, returned as Response 
entity"),
+        @ApiResponse(responseCode = "202",
+                description = "Batch results not yet available, retry later", 
headers = {
+                    @Header(name = HttpHeaders.LOCATION, schema =
+                            @Schema(type = "string"),
+                            description = "URL to poll in order to get the 
results of the requested batch processing"),
+                    @Header(name = HttpHeaders.RETRY_AFTER, schema =
+                            @Schema(type = "integer"),
+                            description = "seconds after which attempt again 
to get batch results") }),
+        @ApiResponse(responseCode = "404", description = "No batch process was 
found for the provided boundary") })
+    @Path("/batch")
+    @Produces(RESTHeaders.MULTIPART_MIXED)
+    Response batch();
+
+    /**
      * Returns the list of Groups, according to provided paging instructions, 
assignable to Users and Any Objects of
      * the provided Realm.
      *
@@ -94,7 +156,7 @@ public interface SyncopeService extends JAXRSService {
      */
     @POST
     @Path("/assignableGroups/{realm:.*}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     PagedResult<GroupTO> searchAssignableGroups(
             @NotNull @PathParam("realm") String realm,
             @QueryParam("term") String term,
@@ -109,6 +171,6 @@ public interface SyncopeService extends JAXRSService {
      */
     @GET
     @Path("/userTypeExtension/{groupName}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     TypeExtensionTO readUserTypeExtension(@NotNull @PathParam("groupName") 
String groupName);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
index b74ede1..70f5935 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
@@ -42,7 +42,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.to.BulkAction;
 import org.apache.syncope.common.lib.to.BulkActionResult;
@@ -73,7 +72,7 @@ public interface TaskService extends ExecutableService {
      */
     @GET
     @Path("{type}/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     <T extends TaskTO> T read(
             @NotNull @PathParam("type") TaskType type,
             @NotNull @PathParam("key") String key,
@@ -88,7 +87,7 @@ public interface TaskService extends ExecutableService {
      */
     @GET
     @Path("{type}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     <T extends TaskTO> PagedResult<T> search(@BeanParam TaskQuery query);
 
     /**
@@ -109,8 +108,8 @@ public interface TaskService extends ExecutableService {
                         description = "URL of the entity created") }))
     @POST
     @Path("{type}")
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response create(@NotNull @PathParam("type") TaskType type, @NotNull 
SchedTaskTO taskTO);
 
     /**
@@ -125,8 +124,8 @@ public interface TaskService extends ExecutableService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @PUT
     @Path("{type}/{key}")
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void update(@NotNull @PathParam("type") TaskType type, @NotNull 
SchedTaskTO taskTO);
 
     /**
@@ -139,7 +138,7 @@ public interface TaskService extends ExecutableService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @DELETE
     @Path("{type}/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("type") TaskType type, @NotNull 
@PathParam("key") String key);
 
     /**
@@ -150,7 +149,7 @@ public interface TaskService extends ExecutableService {
      */
     @POST
     @Path("bulk")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     BulkActionResult bulk(@NotNull BulkAction bulkAction);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
index fd1665a..b655e8b 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
@@ -42,7 +42,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
@@ -77,7 +76,7 @@ public interface UserSelfService extends JAXRSService {
                         description = "List of entitlements owned by the 
calling user")
             }))
     @GET
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response read();
 
     /**
@@ -110,8 +109,8 @@ public interface UserSelfService extends JAXRSService {
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference 
was applied") }))
     @POST
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response create(@NotNull UserTO userTO,
             @DefaultValue("true") @QueryParam("storePassword") boolean 
storePassword);
 
@@ -144,8 +143,8 @@ public interface UserSelfService extends JAXRSService {
                         + "client about the fact that a specified preference 
was applied")) })
     @PATCH
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response update(@NotNull UserPatch patch);
 
     /**
@@ -177,8 +176,8 @@ public interface UserSelfService extends JAXRSService {
                         + "client about the fact that a specified preference 
was applied")) })
     @PUT
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response update(@NotNull UserTO user);
 
     /**
@@ -210,8 +209,8 @@ public interface UserSelfService extends JAXRSService {
                         + "client about the fact that a specified preference 
was applied")) })
     @POST
     @Path("{key}/status")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response status(@NotNull StatusPatch statusPatch);
 
     /**
@@ -223,7 +222,7 @@ public interface UserSelfService extends JAXRSService {
         @SecurityRequirement(name = "BasicAuthentication"),
         @SecurityRequirement(name = "Bearer") })
     @DELETE
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response delete();
 
     /**
@@ -238,7 +237,7 @@ public interface UserSelfService extends JAXRSService {
         @SecurityRequirement(name = "Bearer") })
     @POST
     @Path("mustChangePassword")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response mustChangePassword(String password);
 
     /**
@@ -253,7 +252,7 @@ public interface UserSelfService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @POST
     @Path("requestPasswordReset")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void requestPasswordReset(@NotNull @QueryParam("username") String 
username, String securityAnswer);
 
     /**
@@ -269,6 +268,6 @@ public interface UserSelfService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @POST
     @Path("confirmPasswordReset")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void confirmPasswordReset(@NotNull @QueryParam("token") String token, 
String password);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
index af33157..0274acd 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
@@ -40,7 +40,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.PagedResult;
@@ -108,8 +107,8 @@ public interface UserService extends AnyService<UserTO> {
                         description = "Allows the server to inform the "
                         + "client about the fact that a specified preference 
was applied") }))
     @POST
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response create(
             @NotNull UserTO userTO,
             @DefaultValue("true") @QueryParam("storePassword") boolean 
storePassword);
@@ -153,8 +152,8 @@ public interface UserService extends AnyService<UserTO> {
                 + " date of the entity") })
     @PATCH
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response update(@NotNull UserPatch userPatch);
 
     /**
@@ -197,8 +196,8 @@ public interface UserService extends AnyService<UserTO> {
                 + " date of the entity") })
     @PUT
     @Path("{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response update(@NotNull UserTO userTO);
 
     /**
@@ -241,7 +240,7 @@ public interface UserService extends AnyService<UserTO> {
                 + " date of the entity") })
     @POST
     @Path("{key}/status")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response status(@NotNull StatusPatch statusPatch);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowService.java
index ee65b7e..c38746f 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowService.java
@@ -31,11 +31,11 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.WorkflowFormTO;
 import org.apache.syncope.common.lib.to.WorkflowTaskTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.WorkflowFormQuery;
 
 /**
@@ -56,7 +56,7 @@ public interface UserWorkflowService extends JAXRSService {
      */
     @GET
     @Path("forms")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     PagedResult<WorkflowFormTO> getForms(@BeanParam WorkflowFormQuery query);
 
     /**
@@ -67,7 +67,7 @@ public interface UserWorkflowService extends JAXRSService {
      */
     @GET
     @Path("forms/{userKey}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     WorkflowFormTO getFormForUser(@NotNull @PathParam("userKey") String 
userKey);
 
     /**
@@ -78,7 +78,7 @@ public interface UserWorkflowService extends JAXRSService {
      */
     @POST
     @Path("forms/{taskId}/claim")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     WorkflowFormTO claimForm(@NotNull @PathParam("taskId") String taskId);
 
     /**
@@ -89,8 +89,8 @@ public interface UserWorkflowService extends JAXRSService {
      */
     @POST
     @Path("forms")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     UserTO submitForm(@NotNull WorkflowFormTO form);
 
     /**
@@ -112,7 +112,7 @@ public interface UserWorkflowService extends JAXRSService {
      */
     @POST
     @Path("tasks/{taskId}/execute")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     UserTO executeTask(@NotNull @PathParam("taskId") String taskId, @NotNull 
UserTO userTO);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/WorkflowService.java
----------------------------------------------------------------------
diff --git 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/WorkflowService.java
 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/WorkflowService.java
index 92d026f..25e3f7a 100644
--- 
a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/WorkflowService.java
+++ 
b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/WorkflowService.java
@@ -34,7 +34,6 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.WorkflowDefinitionTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
@@ -56,7 +55,7 @@ public interface WorkflowService extends JAXRSService {
      */
     @GET
     @Path("{anyType}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     List<WorkflowDefinitionTO> list(@NotNull @PathParam("anyType") String 
anyType);
 
     /**
@@ -68,7 +67,7 @@ public interface WorkflowService extends JAXRSService {
      */
     @GET
     @Path("{anyType}/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     Response get(
             @NotNull @PathParam("anyType") String anyType,
             @NotNull @PathParam("key") String key);
@@ -98,8 +97,8 @@ public interface WorkflowService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @PUT
     @Path("{anyType}/{key}")
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void set(
             @NotNull @PathParam("anyType") String anyType,
             @NotNull @PathParam("key") String key,
@@ -115,7 +114,7 @@ public interface WorkflowService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @DELETE
     @Path("{anyType}/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(
             @NotNull @PathParam("anyType") String anyType,
             @NotNull @PathParam("key") String key);

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/BatchDAO.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/BatchDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/BatchDAO.java
new file mode 100644
index 0000000..b5e1166
--- /dev/null
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/BatchDAO.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao;
+
+import org.apache.syncope.core.persistence.api.entity.Batch;
+
+public interface BatchDAO extends DAO<Batch> {
+
+    Batch find(String key);
+
+    Batch save(Batch batch);
+
+    void delete(String key);
+
+    int deleteExpired();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Batch.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Batch.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Batch.java
new file mode 100644
index 0000000..86fa647
--- /dev/null
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Batch.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+import java.util.Date;
+
+public interface Batch extends ProvidedKeyEntity {
+
+    Date getExpiryTime();
+
+    void setExpiryTime(Date expiryTime);
+
+    String getResults();
+
+    void setResults(String results);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPABatchDAO.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPABatchDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPABatchDAO.java
new file mode 100644
index 0000000..7470c5a
--- /dev/null
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPABatchDAO.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.Date;
+import javax.persistence.Query;
+import org.apache.syncope.core.persistence.api.dao.BatchDAO;
+import org.apache.syncope.core.persistence.api.entity.Batch;
+import org.apache.syncope.core.persistence.jpa.entity.JPABatch;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(rollbackFor = Throwable.class)
+@Repository
+public class JPABatchDAO extends AbstractDAO<Batch> implements BatchDAO {
+
+    @Transactional(readOnly = true)
+    @Override
+    public Batch find(final String key) {
+        return entityManager().find(JPABatch.class, key);
+    }
+
+    @Override
+    public Batch save(final Batch batch) {
+        return entityManager().merge(batch);
+    }
+
+    @Override
+    public void delete(final String key) {
+        Batch batch = find(key);
+        if (batch == null) {
+            return;
+        }
+
+        entityManager().remove(batch);
+    }
+
+    @Override
+    public int deleteExpired() {
+        Query query = entityManager().createQuery(
+                "DELETE FROM " + JPABatch.class.getSimpleName() + " e "
+                + "WHERE e.expiryTime < :now");
+        query.setParameter("now", new Date());
+        return query.executeUpdate();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPABatch.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPABatch.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPABatch.java
new file mode 100644
index 0000000..26c068c
--- /dev/null
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPABatch.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity;
+
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import org.apache.syncope.core.persistence.api.entity.Batch;
+
+@Entity
+@Table(name = JPABatch.TABLE)
+public class JPABatch extends AbstractProvidedKeyEntity implements Batch {
+
+    private static final long serialVersionUID = 468423182798249255L;
+
+    public static final String TABLE = "SyncopeBatch";
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date expiryTime;
+
+    @Lob
+    private String results;
+
+    @Override
+    public Date getExpiryTime() {
+        return expiryTime == null
+                ? null
+                : new Date(expiryTime.getTime());
+    }
+
+    @Override
+    public void setExpiryTime(final Date expiryTime) {
+        if (expiryTime == null) {
+            this.expiryTime = null;
+        } else {
+            this.expiryTime = new Date(expiryTime.getTime());
+        }
+    }
+
+    @Override
+    public String getResults() {
+        return results;
+    }
+
+    @Override
+    public void setResults(final String results) {
+        this.results = results;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 0f60dd7..1fa7787 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -31,6 +31,7 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.Batch;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ConnInstanceHistoryConf;
 import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
@@ -295,6 +296,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAImplementation();
         } else if (reference.equals(Remediation.class)) {
             result = (E) new JPARemediation();
+        } else if (reference.equals(Batch.class)) {
+            result = (E) new JPABatch();
         } else {
             throw new IllegalArgumentException("Could not find a JPA 
implementation of " + reference.getName());
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
index 25c5b2a..123e500 100644
--- a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
@@ -194,6 +194,10 @@ under the License.
                   
body="org.apache.syncope.core.provisioning.java.job.ExpiredAccessTokenCleanup"/>
   <Task DTYPE="SchedTask" id="89de5014-e3f5-4462-84d8-d97575740baf" 
name="Access Token Cleanup Task"  active="1"
         jobDelegate_id="ExpiredAccessTokenCleanup" cronExpression="0 0/5 * * * 
?"/>
+  <Implementation id="ExpiredBatchCleanup" type="TASKJOB_DELEGATE" 
engine="JAVA"
+                  
body="org.apache.syncope.core.provisioning.java.job.ExpiredBatchCleanup"/>
+  <Task DTYPE="SchedTask" id="8ea0ea51-ce08-4fe3-a0c8-c281b31b5893" 
name="Access Token Cleanup Task"  active="1"
+        jobDelegate_id="ExpiredBatchCleanup" cronExpression="0 0/5 * * * ?"/>
 
   <!-- Password reset notifications -->
   <MailTemplate id="requestPasswordReset"

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
index 2f29e17..f505fbe 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
@@ -43,7 +43,7 @@ public class ImplementationTest extends AbstractTest {
         List<Implementation> implementations = implementationDAO.findAll();
         assertFalse(implementations.isEmpty());
 
-        assertEquals(18, implementations.size());
+        assertEquals(19, implementations.size());
 
         implementations = 
implementationDAO.find(ImplementationType.PULL_ACTIONS);
         assertEquals(1, implementations.size());
@@ -52,7 +52,7 @@ public class ImplementationTest extends AbstractTest {
         assertEquals(1, implementations.size());
 
         implementations = 
implementationDAO.find(ImplementationType.TASKJOB_DELEGATE);
-        assertEquals(5, implementations.size());
+        assertEquals(6, implementations.size());
 
         implementations = implementationDAO.find(ImplementationType.REPORTLET);
         assertEquals(2, implementations.size());

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
index 803984e..a912d42 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
@@ -97,7 +97,7 @@ public class TaskTest extends AbstractTest {
     public void findAll() {
         assertEquals(5, taskDAO.findAll(TaskType.PROPAGATION).size());
         assertEquals(1, taskDAO.findAll(TaskType.NOTIFICATION).size());
-        assertEquals(3, taskDAO.findAll(TaskType.SCHEDULED).size());
+        assertEquals(4, taskDAO.findAll(TaskType.SCHEDULED).size());
         assertEquals(10, taskDAO.findAll(TaskType.PULL).size());
         assertEquals(11, taskDAO.findAll(TaskType.PUSH).size());
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 6096d05..1f0fd44 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -1333,6 +1333,10 @@ under the License.
   <Task DTYPE="PullTask" remediation="0" 
id="30cfd653-257b-495f-8665-281281dbcb3d" name="Scripted SQL" 
resource_id="resource-db-scripted"
         destinationRealm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" 
performCreate="1" performUpdate="1" performDelete="0" syncStatus="0" 
pullMode="INCREMENTAL"
         unmatchingRule="PROVISION" matchingRule="UPDATE" active="1" 
jobDelegate_id="PullJobDelegate"/>
+  <Implementation id="ExpiredBatchCleanup" type="TASKJOB_DELEGATE" 
engine="JAVA"
+                  
body="org.apache.syncope.core.provisioning.java.job.ExpiredBatchCleanup"/>
+  <Task DTYPE="SchedTask" id="8ea0ea51-ce08-4fe3-a0c8-c281b31b5893" 
name="Access Token Cleanup Task"  active="1"
+        jobDelegate_id="ExpiredBatchCleanup" cronExpression="0 0/5 * * * ?"/>
 
   <MailTemplate id="requestPasswordReset"
                 textTemplate="Hi,

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
index 327b396..351d21d 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
@@ -87,9 +87,9 @@ public class AccessTokenDataBinderImpl implements 
AccessTokenDataBinder {
         jwtClaims.setIssuer(jwtIssuer);
         jwtClaims.setExpiryTime(expiryTime);
         jwtClaims.setNotBefore(currentTime);
-        for (Map.Entry<String, Object> entry : claims.entrySet()) {
-            jwtClaims.setClaim(entry.getKey(), entry.getValue());
-        }
+        claims.forEach((key, value) -> {
+            jwtClaims.setClaim(key, value);
+        });
 
         JwsHeaders jwsHeaders = new JwsHeaders(JoseType.JWT, 
jwsSignatureProvider.getAlgorithm());
         JwtToken token = new JwtToken(jwsHeaders, jwtClaims);

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredBatchCleanup.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredBatchCleanup.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredBatchCleanup.java
new file mode 100644
index 0000000..9d8e7bc
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredBatchCleanup.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job;
+
+import org.apache.syncope.core.persistence.api.dao.BatchDAO;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class ExpiredBatchCleanup extends AbstractSchedTaskJobDelegate {
+
+    @Autowired
+    private BatchDAO batchDAO;
+
+    @Override
+    protected String doExecute(final boolean dryRun) throws 
JobExecutionException {
+        if (!dryRun) {
+            int deleted = batchDAO.deleteExpired();
+            LOG.debug("Successfully deleted {} expired batch requests", 
deleted);
+        }
+
+        return "SUCCESS";
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/pom.xml
----------------------------------------------------------------------
diff --git a/core/rest-cxf/pom.xml b/core/rest-cxf/pom.xml
index 23833d6..d56efe0 100644
--- a/core/rest-cxf/pom.xml
+++ b/core/rest-cxf/pom.xml
@@ -154,6 +154,17 @@ under the License.
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
+      
+      <plugin>
+        <groupId>org.gaul</groupId>
+        <artifactId>modernizer-maven-plugin</artifactId>
+        <configuration>
+          <exclusions>
+            <!-- required by HttpServletRequest's override in BatchItemRequest 
-->
+            java/lang/StringBuffer."&lt;init&gt;":(Ljava/lang/String;)V
+          </exclusions>
+        </configuration>
+      </plugin>
     </plugins>
 
     <resources>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemRequest.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemRequest.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemRequest.java
new file mode 100644
index 0000000..cc1cd22
--- /dev/null
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemRequest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.rest.cxf.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.stream.Collectors;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.ws.rs.core.HttpHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.springframework.http.MediaType;
+
+public class BatchItemRequest extends HttpServletRequestWrapper {
+
+    private final String basePath;
+
+    private final BatchRequestItem batchItem;
+
+    private final ServletInputStream inputStream;
+
+    public BatchItemRequest(
+            final String basePath,
+            final HttpServletRequest request,
+            final BatchRequestItem batchItem) {
+
+        super(request);
+        this.basePath = basePath;
+        this.batchItem = batchItem;
+        this.inputStream = new ServletInputStream() {
+
+            private final ByteArrayInputStream bais = new 
ByteArrayInputStream(batchItem.getContent().getBytes());
+
+            private boolean isFinished = false;
+
+            private boolean isReady = true;
+
+            @Override
+            public boolean isFinished() {
+                return isFinished;
+            }
+
+            @Override
+            public boolean isReady() {
+                return isReady;
+            }
+
+            @Override
+            public void setReadListener(final ReadListener readListener) {
+                // nope
+            }
+
+            @Override
+            public int read() {
+                isFinished = true;
+                isReady = false;
+                return bais.read();
+            }
+        };
+    }
+
+    @Override
+    public String getMethod() {
+        return batchItem.getMethod();
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        return new StringBuffer(basePath).append(getRequestURI());
+    }
+
+    @Override
+    public String getRequestURI() {
+        return batchItem.getRequestURI();
+    }
+
+    @Override
+    public String getQueryString() {
+        return batchItem.getQueryString();
+    }
+
+    @Override
+    public String getContentType() {
+        return batchItem.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE)
+                ? 
batchItem.getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0).toString()
+                : MediaType.ALL_VALUE;
+    }
+
+    @Override
+    public int getContentLength() {
+        return batchItem.getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)
+                ? 
Integer.valueOf(batchItem.getHeaders().get(HttpHeaders.CONTENT_LENGTH).get(0).toString())
+                : 0;
+    }
+
+    @Override
+    public long getContentLengthLong() {
+        return getContentLength();
+    }
+
+    @Override
+    public String getHeader(final String name) {
+        return batchItem.getHeaders().containsKey(name)
+                ? batchItem.getHeaders().get(name).get(0).toString()
+                : HttpHeaders.CONTENT_TYPE.equals(name) || 
HttpHeaders.ACCEPT.equals(name)
+                ? MediaType.ALL_VALUE
+                : super.getHeader(name);
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(final String name) {
+        return batchItem.getHeaders().containsKey(name)
+                ? Collections.enumeration(
+                        
batchItem.getHeaders().get(name).stream().map(Object::toString).collect(Collectors.toList()))
+                : HttpHeaders.CONTENT_TYPE.equals(name) || 
HttpHeaders.ACCEPT.equals(name)
+                ? Collections.enumeration(Arrays.asList(MediaType.ALL_VALUE))
+                : super.getHeaders(name);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        return inputStream;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemResponse.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemResponse.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemResponse.java
new file mode 100644
index 0000000..e16fed9
--- /dev/null
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchItemResponse.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.rest.cxf.batch;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+
+public class BatchItemResponse implements HttpServletResponse {
+
+    private final Set<Cookie> cookies = new HashSet<>();
+
+    private final Map<String, List<Object>> headers = new HashMap<>();
+
+    private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+    private final ServletOutputStream servletOuputStream = new 
ServletOutputStream() {
+
+        @Override
+        public boolean isReady() {
+            return true;
+        }
+
+        @Override
+        public void setWriteListener(final WriteListener writeListener) {
+            // nope
+        }
+
+        @Override
+        public void write(final int b) throws IOException {
+            baos.write(b);
+        }
+    };
+
+    private final PrintWriter writer = new PrintWriter(baos);
+
+    private int status;
+
+    private Locale locale;
+
+    public Set<Cookie> getCookies() {
+        return cookies;
+    }
+
+    public Map<String, List<Object>> getHeaders() {
+        return headers;
+    }
+
+    @Override
+    public void addCookie(final Cookie cookie) {
+        this.cookies.add(cookie);
+    }
+
+    @Override
+    public boolean containsHeader(final String name) {
+        return headers.containsKey(name);
+    }
+
+    @Override
+    public void setDateHeader(final String name, final long date) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        } else {
+            values.clear();
+        }
+        values.add(date);
+    }
+
+    @Override
+    public void addDateHeader(final String name, final long date) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        }
+        values.add(date);
+    }
+
+    @Override
+    public void setHeader(final String name, final String value) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        } else {
+            values.clear();
+        }
+        values.add(value);
+    }
+
+    @Override
+    public void addHeader(final String name, final String value) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        }
+        values.add(value);
+    }
+
+    @Override
+    public void setIntHeader(final String name, final int value) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        } else {
+            values.clear();
+        }
+        values.add(value);
+    }
+
+    @Override
+    public void addIntHeader(final String name, final int value) {
+        List<Object> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<>();
+            headers.put(name, values);
+        }
+        values.add(value);
+    }
+
+    @Override
+    public String getHeader(final String name) {
+        return headers.containsKey(name) ? headers.get(name).get(0).toString() 
: null;
+    }
+
+    @Override
+    public Collection<String> getHeaders(final String name) {
+        return headers.containsKey(name)
+                ? 
headers.get(name).stream().map(Object::toString).collect(Collectors.toList())
+                : Collections.emptyList();
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        return headers.keySet();
+    }
+
+    @Override
+    public String encodeURL(final String url) {
+        return url;
+    }
+
+    @Override
+    public String encodeRedirectURL(final String url) {
+        return url;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String encodeUrl(final String url) {
+        return encodeURL(url);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String encodeRedirectUrl(final String url) {
+        return encodeRedirectURL(url);
+    }
+
+    @Override
+    public void sendError(final int sc, final String msg) throws IOException {
+        setStatus(sc);
+    }
+
+    @Override
+    public void sendError(final int sc) throws IOException {
+        setStatus(sc);
+    }
+
+    @Override
+    public void sendRedirect(final String location) throws IOException {
+        setStatus(SC_MOVED_TEMPORARILY);
+        setHeader(HttpHeaders.LOCATION, location);
+    }
+
+    @Override
+    public void setStatus(final int sc) {
+        this.status = sc;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public void setStatus(final int sc, final String sm) {
+        setStatus(sc);
+    }
+
+    @Override
+    public int getStatus() {
+        return status;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContentType() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ByteArrayOutputStream getUnderlyingOutputStream() {
+        return baos;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return servletOuputStream;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        return writer;
+    }
+
+    @Override
+    public void setCharacterEncoding(final String charset) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setContentLength(final int len) {
+        setIntHeader(HttpHeaders.CONTENT_LENGTH, len);
+    }
+
+    @Override
+    public void setContentLengthLong(final long len) {
+        setContentLength((int) len);
+    }
+
+    @Override
+    public void setContentType(final String type) {
+        setHeader(HttpHeaders.CONTENT_TYPE, type);
+    }
+
+    @Override
+    public void setBufferSize(final int size) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getBufferSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void flushBuffer() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void resetBuffer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isCommitted() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void reset() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setLocale(final Locale loc) {
+        this.locale = loc;
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchProcess.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchProcess.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchProcess.java
new file mode 100644
index 0000000..a76d418
--- /dev/null
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/batch/BatchProcess.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.rest.cxf.batch;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.ServletConfig;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
+import org.apache.cxf.transport.http.DestinationRegistry;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.rest.api.batch.BatchPayloadGenerator;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
+import org.apache.syncope.core.persistence.api.dao.BatchDAO;
+import org.apache.syncope.core.persistence.api.entity.Batch;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class BatchProcess implements Runnable {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(BatchProcess.class);
+
+    @Autowired
+    private BatchDAO batchDAO;
+
+    private String boundary;
+
+    private String basePath;
+
+    private List<BatchRequestItem> batchRequestItems;
+
+    private DestinationRegistry destinationRegistry;
+
+    private ServletConfig servletConfig;
+
+    private HttpServletRequest servletRequest;
+
+    private Authentication authentication;
+
+    public void setBoundary(final String boundary) {
+        this.boundary = boundary;
+    }
+
+    public void setBasePath(final String basePath) {
+        this.basePath = basePath;
+    }
+
+    public void setBatchRequestItems(final List<BatchRequestItem> 
batchRequestItems) {
+        this.batchRequestItems = batchRequestItems;
+    }
+
+    public void setDestinationRegistry(final DestinationRegistry 
destinationRegistry) {
+        this.destinationRegistry = destinationRegistry;
+    }
+
+    public void setServletConfig(final ServletConfig servletConfig) {
+        this.servletConfig = servletConfig;
+    }
+
+    public void setServletRequest(final HttpServletRequest servletRequest) {
+        this.servletRequest = servletRequest;
+    }
+
+    public void setAuthentication(final Authentication authentication) {
+        this.authentication = authentication;
+    }
+
+    @Override
+    public void run() {
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+
+        List<BatchResponseItem> batchResponseItems = new 
ArrayList<>(batchRequestItems.size());
+
+        batchRequestItems.forEach((BatchRequestItem reqItem) -> {
+            LOG.debug("Batch item:\n{}", reqItem);
+
+            AbstractHTTPDestination dest = 
destinationRegistry.getDestinationForPath(reqItem.getRequestURI(), true);
+            if (dest == null) {
+                dest = 
destinationRegistry.checkRestfulRequest(reqItem.getRequestURI());
+            }
+            LOG.debug("Destination found for {}: {}", reqItem.getRequestURI(), 
dest);
+
+            if (dest == null) {
+                BatchResponseItem resItem = new BatchResponseItem();
+                resItem.setStatus(404);
+                batchResponseItems.add(resItem);
+            } else {
+                BatchItemRequest request = new BatchItemRequest(basePath, 
servletRequest, reqItem);
+                BatchItemResponse response = new BatchItemResponse();
+                try {
+                    dest.invoke(servletConfig, 
servletConfig.getServletContext(), request, response);
+                    LOG.debug("Returned:\nstatus: {}\nheaders: {}\nbody:\n{}", 
response.getStatus(),
+                            response.getHeaders(), new 
String(response.getUnderlyingOutputStream().toByteArray()));
+
+                    BatchResponseItem resItem = new BatchResponseItem();
+                    resItem.setStatus(response.getStatus());
+                    resItem.setHeaders(response.getHeaders());
+                    String output = new 
String(response.getUnderlyingOutputStream().toByteArray());
+                    if (output.length() > 0) {
+                        resItem.setContent(output);
+                    }
+                    batchResponseItems.add(resItem);
+                } catch (IOException e) {
+                    LOG.error("Invocation of {} failed", dest.getPath(), e);
+
+                    BatchResponseItem resItem = new BatchResponseItem();
+                    resItem.setStatus(404);
+                    batchResponseItems.add(resItem);
+                }
+            }
+        });
+
+        String results = BatchPayloadGenerator.generate(batchResponseItems, 
SyncopeConstants.DOUBLE_DASH + boundary);
+
+        Batch batch = batchDAO.find(boundary);
+        if (batch == null) {
+            LOG.error("Could not find batch {}, cannot save results hence 
reporting here:\n{}", boundary, results);
+        } else {
+            batch.setResults(results);
+            batchDAO.save(batch);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
index caef5d8..2e9a0c0 100644
--- 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
@@ -80,7 +80,8 @@ abstract class AbstractServiceImpl implements JAXRSService {
     }
 
     protected boolean isNullPriorityAsync() {
-        return 
BooleanUtils.toBoolean(messageContext.getHttpHeaders().getHeaderString(RESTHeaders.NULL_PRIORITY_ASYNC));
+        return BooleanUtils.toBoolean(
+                
messageContext.getHttpServletRequest().getHeader(RESTHeaders.NULL_PRIORITY_ASYNC));
     }
 
     /**
@@ -89,8 +90,8 @@ abstract class AbstractServiceImpl implements JAXRSService {
      * @return a {@code Preference} instance matching the passed {@code 
Prefer} header,
      * or {@code Preference.NONE} if missing.
      */
-    private Preference getPreference() {
-        return 
Preference.fromString(messageContext.getHttpHeaders().getHeaderString(RESTHeaders.PREFER));
+    protected Preference getPreference() {
+        return 
Preference.fromString(messageContext.getHttpServletRequest().getHeader(RESTHeaders.PREFER));
     }
 
     protected Response.ResponseBuilder applyPreference(

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
index 2f84efc..1deaa37 100644
--- 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
@@ -18,9 +18,23 @@
  */
 package org.apache.syncope.core.rest.cxf.service;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
 import java.util.List;
+import javax.annotation.Resource;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.Bus;
+import org.apache.cxf.transport.DestinationFactoryManager;
+import org.apache.cxf.transport.http.DestinationRegistry;
+import org.apache.cxf.transport.http.HTTPTransportFactory;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.NumbersInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
@@ -28,17 +42,41 @@ import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.Preference;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.core.logic.SyncopeLogic;
+import org.apache.syncope.core.rest.cxf.batch.BatchProcess;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.apache.syncope.core.persistence.api.dao.BatchDAO;
+import org.apache.syncope.core.persistence.api.entity.Batch;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.security.core.context.SecurityContextHolder;
 
 @Service
 public class SyncopeServiceImpl extends AbstractServiceImpl implements 
SyncopeService {
 
+    @Resource(name = "batchExecutor")
+    private ThreadPoolTaskExecutor batchExecutor;
+
     @Autowired
     private SyncopeLogic logic;
 
+    @Autowired
+    private Bus bus;
+
+    @Autowired
+    private BatchDAO batchDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
     @Override
     public PlatformInfo platform() {
         return logic.platform();
@@ -68,4 +106,95 @@ public class SyncopeServiceImpl extends AbstractServiceImpl 
implements SyncopeSe
         return logic.readTypeExtension(groupName);
     }
 
+    private DestinationRegistry getDestinationRegistryFromBusOrDefault() {
+        DestinationFactoryManager dfm = 
bus.getExtension(DestinationFactoryManager.class);
+        try {
+            HTTPTransportFactory df = (HTTPTransportFactory) dfm.
+                    
getDestinationFactory("http://cxf.apache.org/transports/http/configuration";);
+            return df.getRegistry();
+        } catch (Exception e) {
+            throw new InternalServerErrorException("Could not find CXF's 
DestinationRegistry", e);
+        }
+    }
+
+    @Override
+    public Response batch(final InputStream input) {
+        // parse Content-Type, expect appropriate boundary
+        MediaType mediaType = 
MediaType.valueOf(messageContext.getHttpServletRequest().getContentType());
+        String boundary = 
mediaType.getParameters().get(RESTHeaders.BOUNDARY_PARAMETER);
+
+        if (batchDAO.find(boundary) != null) {
+            SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.EntityExists);
+            sce.getElements().add("Batch with boundary " + boundary + " 
already processing");
+            throw sce;
+        }
+
+        // parse batch request
+        List<BatchRequestItem> batchRequestItems;
+        try {
+            batchRequestItems = BatchPayloadParser.parse(input, mediaType, new 
BatchRequestItem());
+        } catch (IOException e) {
+            LOG.error("Could not parse batch request with boundary {}", 
boundary, e);
+
+            SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.InvalidEntity);
+            sce.getElements().add("Batch request with boundary " + boundary);
+            throw sce;
+        }
+
+        // prepare for batch processing
+        Batch batch = entityFactory.newEntity(Batch.class);
+        batch.setKey(boundary);
+        batch.setExpiryTime(new Date(System.currentTimeMillis() + 5 * 60 * 
1000));
+        batchDAO.save(batch);
+
+        BatchProcess batchProcess = 
ApplicationContextProvider.getBeanFactory().createBean(BatchProcess.class);
+        batchProcess.setBoundary(boundary);
+        batchProcess.setBasePath(uriInfo.getBaseUri().toASCIIString());
+        batchProcess.setBatchRequestItems(batchRequestItems);
+        
batchProcess.setDestinationRegistry(getDestinationRegistryFromBusOrDefault());
+        batchProcess.setServletConfig(messageContext.getServletConfig());
+        batchProcess.setServletRequest(messageContext.getHttpServletRequest());
+        
batchProcess.setAuthentication(SecurityContextHolder.getContext().getAuthentication());
+
+        // manage synchronous Vs asynchronous batch processing
+        if (getPreference() == Preference.RESPOND_ASYNC) {
+            batchExecutor.execute(batchProcess);
+
+            return Response.accepted().
+                    header(RESTHeaders.PREFERENCE_APPLIED, 
getPreference().toString()).
+                    header(HttpHeaders.LOCATION, 
uriInfo.getAbsolutePathBuilder().build()).
+                    type(RESTHeaders.multipartMixedWith(boundary)).
+                    build();
+        } else {
+            batchProcess.run();
+            return batch();
+        }
+    }
+
+    @Override
+    public Response batch() {
+        MediaType mediaType = 
MediaType.valueOf(messageContext.getHttpServletRequest().getContentType());
+        String boundary = 
mediaType.getParameters().get(RESTHeaders.BOUNDARY_PARAMETER);
+
+        Batch batch = batchDAO.find(boundary);
+        if (batch == null) {
+            throw new NotFoundException("Batch " + boundary);
+        }
+
+        if (batch.getResults() == null) {
+            return Response.accepted().
+                    type(RESTHeaders.multipartMixedWith(boundary)).
+                    header(HttpHeaders.RETRY_AFTER, 5).
+                    header(HttpHeaders.LOCATION, 
uriInfo.getAbsolutePathBuilder().build()).
+                    build();
+        }
+
+        Response response = Response.ok(batch.getResults()).
+                type(RESTHeaders.multipartMixedWith(boundary)).
+                build();
+
+        batchDAO.delete(boundary);
+
+        return response;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/core/rest-cxf/src/main/resources/restCXFContext.xml
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/resources/restCXFContext.xml 
b/core/rest-cxf/src/main/resources/restCXFContext.xml
index 571add9..dd3ff18 100644
--- a/core/rest-cxf/src/main/resources/restCXFContext.xml
+++ b/core/rest-cxf/src/main/resources/restCXFContext.xml
@@ -21,18 +21,23 @@ under the License.
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
        xmlns:jaxrs="http://cxf.apache.org/jaxrs";
        xmlns:context="http://www.springframework.org/schema/context";
+       xmlns:task="http://www.springframework.org/schema/task";
        xsi:schemaLocation="http://www.springframework.org/schema/beans
                            
http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://cxf.apache.org/jaxrs
                            http://cxf.apache.org/schemas/jaxrs.xsd
                            http://www.springframework.org/schema/context
-                           
http://www.springframework.org/schema/context/spring-context.xsd";>
+                           
http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/task
+                           
http://www.springframework.org/schema/task/spring-task.xsd";>
 
   <import resource="classpath:META-INF/cxf/cxf.xml"/>
   <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
 
   <context:component-scan 
base-package="org.apache.syncope.core.rest.cxf.service"/>  
 
+  <task:executor id="batchExecutor" pool-size="10"/>
+
   <bean id="jaxbProvider" 
class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
     <property name="namespacePrefixes">
       <map>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d9250efa/ext/camel/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/CamelRouteService.java
----------------------------------------------------------------------
diff --git 
a/ext/camel/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/CamelRouteService.java
 
b/ext/camel/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/CamelRouteService.java
index 0760567..020b4a2 100644
--- 
a/ext/camel/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/CamelRouteService.java
+++ 
b/ext/camel/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/CamelRouteService.java
@@ -36,10 +36,10 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.CamelMetrics;
 import org.apache.syncope.common.lib.to.CamelRouteTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 
 /**
  * REST operations for Camel routes.
@@ -59,7 +59,7 @@ public interface CamelRouteService extends JAXRSService {
      */
     @GET
     @Path("{anyTypeKind}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     List<CamelRouteTO> list(@NotNull @PathParam("anyTypeKind") AnyTypeKind 
anyTypeKind);
 
     /**
@@ -71,7 +71,7 @@ public interface CamelRouteService extends JAXRSService {
      */
     @GET
     @Path("{anyTypeKind}/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     CamelRouteTO read(
             @NotNull @PathParam("anyTypeKind") AnyTypeKind anyTypeKind,
             @NotNull @PathParam("key") String key);
@@ -88,8 +88,8 @@ public interface CamelRouteService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @PUT
     @Path("{anyTypeKind}/{key}")
-    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void update(@NotNull @PathParam("anyTypeKind") AnyTypeKind anyTypeKind, 
@NotNull CamelRouteTO route);
 
     /**
@@ -99,7 +99,7 @@ public interface CamelRouteService extends JAXRSService {
             @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
     @POST
     @Path("restartContext")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void restartContext();
 
     /**
@@ -109,6 +109,6 @@ public interface CamelRouteService extends JAXRSService {
      */
     @GET
     @Path("metrics")
-    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     CamelMetrics metrics();
 }

Reply via email to