You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2019/04/04 15:56:42 UTC

[syncope] branch master updated: [SYNCOPE-1455] Core support for SRA gateway routes

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 891a110  [SYNCOPE-1455] Core support for SRA gateway routes
891a110 is described below

commit 891a110b9fc3e32c0362d649ad488ab47174c1c9
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Apr 4 17:56:30 2019 +0200

    [SYNCOPE-1455] Core support for SRA gateway routes
---
 .../syncope/common/lib/to/GatewayRouteTO.java      | 132 +++++++++++++++++++
 .../syncope/common/lib/types/AMEntitlement.java    |  60 +++++++++
 .../syncope/common/lib/types/FilterFactory.java    |  53 ++++++++
 .../syncope/common/lib/types/GatewayFilter.java    |  93 +++++++++++++
 .../syncope/common/lib/types/GatewayPredicate.java | 110 ++++++++++++++++
 .../common/lib/types/GatewayRouteStatus.java       |  30 +++++
 .../syncope/common/lib/types/PredicateCond.java    |  29 +++++
 .../syncope/common/lib/types/PredicateFactory.java |  38 ++++++
 .../rest/api/service/GatewayRouteService.java      | 144 +++++++++++++++++++++
 core/{persistence-api => am/logic}/pom.xml         |  79 +++++------
 .../syncope/core/logic/GatewayRouteLogic.java      | 132 +++++++++++++++++++
 .../core/logic/init/AMEntitlementLoader.java       |  38 ++++++
 core/am/pom.xml                                    |  44 +++++++
 core/{persistence-api => am/rest-cxf}/pom.xml      |  70 +++++-----
 .../rest/cxf/service/GatewayRouteServiceImpl.java  |  75 +++++++++++
 .../apache/syncope/core/logic/LogicContext.java    |  11 +-
 .../apache/syncope/core/logic/MemoryAppender.java  |   3 +-
 core/persistence-api/pom.xml                       |   5 +
 .../core/persistence/api/dao/GatewayRouteDAO.java  |  35 +++++
 .../core/persistence/api/entity/GatewayRoute.java  |  48 +++++++
 .../persistence/jpa/dao/JPAGatewayRouteDAO.java    |  67 ++++++++++
 .../persistence/jpa/entity/JPAEntityFactory.java   |   3 +
 .../persistence/jpa/entity/JPAGatewayRoute.java    | 116 +++++++++++++++++
 .../jpa/validation/entity/GatewayRouteCheck.java   |  41 ++++++
 .../validation/entity/GatewayRouteValidator.java   |  54 ++++++++
 .../persistence/jpa/inner/GatewayRouteTest.java    |  97 ++++++++++++++
 .../src/test/resources/domains/MasterContent.xml   |   3 +
 core/pom.xml                                       |   1 +
 .../api/data/GatewayRouteDataBinder.java           |  29 +++++
 .../java/data/GatewayRouteDataBinderImpl.java      |  50 +++++++
 fit/core-reference/pom.xml                         |  11 ++
 .../org/apache/syncope/fit/AbstractITCase.java     |   4 +
 .../syncope/fit/core/GatewayRouteITCase.java       | 106 +++++++++++++++
 sra/src/main/resources/application.properties      |   9 ++
 sra/src/main/resources/log4j2.xml                  |  21 ++-
 35 files changed, 1751 insertions(+), 90 deletions(-)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java
new file mode 100644
index 0000000..f973c70
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java
@@ -0,0 +1,132 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.PathParam;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.types.GatewayFilter;
+import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+
+public class GatewayRouteTO implements EntityTO {
+
+    private static final long serialVersionUID = 4044528284951757870L;
+
+    private String key;
+
+    private String name;
+
+    private URI target;
+
+    private final List<GatewayFilter> filters = new ArrayList<>();
+
+    private final List<GatewayPredicate> predicates = new ArrayList<>();
+
+    private GatewayRouteStatus status;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @PathParam("key")
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public URI getTarget() {
+        return target;
+    }
+
+    public void setTarget(final URI target) {
+        this.target = target;
+    }
+
+    @XmlElementWrapper(name = "filters")
+    @XmlElement(name = "filter")
+    @JsonProperty("filters")
+    public List<GatewayFilter> getFilters() {
+        return filters;
+    }
+
+    @XmlElementWrapper(name = "predicates")
+    @XmlElement(name = "predicate")
+    @JsonProperty("predicates")
+    public List<GatewayPredicate> getPredicates() {
+        return predicates;
+    }
+
+    public GatewayRouteStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(final GatewayRouteStatus status) {
+        this.status = status;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(key).
+                append(name).
+                append(target).
+                append(filters).
+                append(predicates).
+                append(status).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final GatewayRouteTO other = (GatewayRouteTO) obj;
+        return new EqualsBuilder().
+                append(key, other.key).
+                append(name, other.name).
+                append(target, other.target).
+                append(filters, other.filters).
+                append(predicates, other.predicates).
+                append(status, other.status).
+                build();
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
new file mode 100644
index 0000000..6d7d762
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common.lib.types;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+public final class AMEntitlement {
+
+    public static final String GATEWAY_ROUTE_LIST = "GATEWAY_ROUTE_LIST";
+
+    public static final String GATEWAY_ROUTE_CREATE = "GATEWAY_ROUTE_CREATE";
+
+    public static final String GATEWAY_ROUTE_READ = "GATEWAY_ROUTE_READ";
+
+    public static final String GATEWAY_ROUTE_UPDATE = "GATEWAY_ROUTE_UPDATE";
+
+    public static final String GATEWAY_ROUTE_DELETE = "GATEWAY_ROUTE_DELETE";
+
+    public static final String GATEWAY_ROUTE_PUSH = "GATEWAY_ROUTE_PUSH";
+
+    private static final Set<String> VALUES;
+
+    static {
+        Set<String> values = new TreeSet<>();
+        for (Field field : AMEntitlement.class.getDeclaredFields()) {
+            if (Modifier.isStatic(field.getModifiers()) && String.class.equals(field.getType())) {
+                values.add(field.getName());
+            }
+        }
+        VALUES = Collections.unmodifiableSet(values);
+    }
+
+    public static Set<String> values() {
+        return VALUES;
+    }
+
+    private AMEntitlement() {
+        // private constructor for static utility class
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java
new file mode 100644
index 0000000..1120af9
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum FilterFactory {
+    ADD_REQUEST_HEADER,
+    ADD_REQUEST_PARAMETER,
+    ADD_RESPONSE_HEADER,
+    HYSTRIX,
+    MODIFY_REQUEST_BODY,
+    MODIFY_RESPONSE_BODY,
+    PREFIX_PATH,
+    PRESERVE_HOST_HEADER,
+    REDIRECT,
+    REMOVE_REQUEST_HEADER,
+    REMOVE_RESPONSE_HEADER,
+    REQUEST_RATE_LIMITER,
+    REWRITE_PATH,
+    RETRY,
+    SECURE_HEADERS,
+    SET_PATH,
+    SET_REQUEST_HEADER,
+    SET_RESPONSE_HEADER,
+    REWRITE_RESPONSE_HEADER,
+    SET_STATUS,
+    SAVE_SESSION,
+    STRIP_PREFIX,
+    REQUEST_HEADER_TO_REQUEST_URI,
+    CHANGE_REQUEST_URI,
+    SET_REQIEST_SIZE,
+    FALLBACK_HEADERS,
+    CUSTOM
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java
new file mode 100644
index 0000000..cbf946e
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.common.lib.types;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+public class GatewayFilter implements Serializable {
+
+    private static final long serialVersionUID = -635785645207375128L;
+
+    public static class Builder {
+
+        private final GatewayFilter instance = new GatewayFilter();
+
+        public Builder factory(final FilterFactory factory) {
+            instance.setFactory(factory);
+            return this;
+        }
+
+        public Builder args(final String args) {
+            instance.setArgs(args);
+            return this;
+        }
+
+        public GatewayFilter build() {
+            return instance;
+        }
+    }
+
+    private FilterFactory factory;
+
+    private String args;
+
+    public FilterFactory getFactory() {
+        return factory;
+    }
+
+    public void setFactory(final FilterFactory factory) {
+        this.factory = factory;
+    }
+
+    public String getArgs() {
+        return args;
+    }
+
+    public void setArgs(final String args) {
+        this.args = args;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(factory).
+                append(args).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final GatewayFilter other = (GatewayFilter) obj;
+        return new EqualsBuilder().
+                append(factory, other.factory).
+                append(args, other.args).
+                build();
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java
new file mode 100644
index 0000000..27c6635
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java
@@ -0,0 +1,110 @@
+/*
+ * 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.common.lib.types;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+public class GatewayPredicate implements Serializable {
+
+    private static final long serialVersionUID = -635785645207375128L;
+
+    public static class Builder {
+
+        private final GatewayPredicate instance = new GatewayPredicate();
+
+        public Builder cond(final PredicateCond cond) {
+            instance.setCond(cond);
+            return this;
+        }
+
+        public Builder factory(final PredicateFactory factory) {
+            instance.setFactory(factory);
+            return this;
+        }
+
+        public Builder args(final String args) {
+            instance.setArgs(args);
+            return this;
+        }
+
+        public GatewayPredicate build() {
+            return instance;
+        }
+    }
+
+    private PredicateCond cond;
+
+    private PredicateFactory factory;
+
+    private String args;
+
+    public PredicateCond getCond() {
+        return cond;
+    }
+
+    public void setCond(final PredicateCond cond) {
+        this.cond = cond;
+    }
+
+    public PredicateFactory getFactory() {
+        return factory;
+    }
+
+    public void setFactory(final PredicateFactory factory) {
+        this.factory = factory;
+    }
+
+    public String getArgs() {
+        return args;
+    }
+
+    public void setArgs(final String args) {
+        this.args = args;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(cond).
+                append(factory).
+                append(args).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final GatewayPredicate other = (GatewayPredicate) obj;
+        return new EqualsBuilder().
+                append(cond, other.cond).
+                append(factory, other.factory).
+                append(args, other.args).
+                build();
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteStatus.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteStatus.java
new file mode 100644
index 0000000..ab199e9
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteStatus.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum GatewayRouteStatus {
+    DRAFT,
+    STAGING,
+    PUBLISHED,
+    DEPRECATED
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java
new file mode 100644
index 0000000..93b0bad
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java
@@ -0,0 +1,29 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum PredicateCond {
+    AND,
+    OR,
+    NOT
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java
new file mode 100644
index 0000000..5cf4e77
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum PredicateFactory {
+    AFTER,
+    BEFORE,
+    BETWEEN,
+    COOKIE,
+    HEADER,
+    HOST,
+    METHOD,
+    PATH,
+    QUERY,
+    REMOTE_ADDR,
+    READ_BODY,
+    CUSTOM
+
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java
new file mode 100644
index 0000000..6b957b8
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.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.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.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+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.to.GatewayRouteTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for gateway routes.
+ */
+@Tag(name = "GatewayRoutes")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("gatewayRoutes")
+public interface GatewayRouteService extends JAXRSService {
+
+    /**
+     * Returns a list of all existing routes.
+     *
+     * @return paged list of existing routes matching the given query
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<GatewayRouteTO> list();
+
+    /**
+     * Creates a new route.
+     *
+     * @param routeTO route to be created
+     * @return Response object featuring Location header of created route
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "Route successfully created", headers = {
+                @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+                        @Schema(type = "string"),
+                        description = "UUID generated for the entity created"),
+                @Header(name = HttpHeaders.LOCATION, schema =
+                        @Schema(type = "string"),
+                        description = "URL of the entity created") }))
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    Response create(@NotNull GatewayRouteTO routeTO);
+
+    /**
+     * Returns route with matching key.
+     *
+     * @param key key of route to be read
+     * @return route with matching key
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    GatewayRouteTO read(@NotNull @PathParam("key") String key);
+
+    /**
+     * Updates route with matching key.
+     *
+     * @param routeTO route to be stored
+     */
+    @Parameter(name = "key", description = "Route's key", in = ParameterIn.PATH, schema =
+            @Schema(type = "string"))
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @PUT
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void update(@NotNull GatewayRouteTO routeTO);
+
+    /**
+     * Deletes route with matching key.
+     *
+     * @param key route key
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void delete(@NotNull @PathParam("key") String key);
+
+    /**
+     * Push all routes to SRA.
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @POST
+    @Path("push")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void pushToSRA();
+
+    /**
+     * Push route with matching key to SRA.
+     *
+     * @param key route key
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @POST
+    @Path("push/{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void pushToSRA(@NotNull @PathParam("key") String key);
+}
diff --git a/core/persistence-api/pom.xml b/core/am/logic/pom.xml
similarity index 50%
copy from core/persistence-api/pom.xml
copy to core/am/logic/pom.xml
index c5a4add..600f0d7 100644
--- a/core/persistence-api/pom.xml
+++ b/core/am/logic/pom.xml
@@ -22,68 +22,57 @@ under the License.
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
-    <groupId>org.apache.syncope</groupId>
-    <artifactId>syncope-core</artifactId>
+    <groupId>org.apache.syncope.core</groupId>
+    <artifactId>syncope-core-am</artifactId>
     <version>3.0.0-SNAPSHOT</version>
   </parent>
 
-  <name>Apache Syncope Core Persistence API</name>
-  <description>Apache Syncope Core Persistence API</description>
-  <groupId>org.apache.syncope.core</groupId>
-  <artifactId>syncope-core-persistence-api</artifactId>
+  <name>Apache Syncope Core AM Logic</name>
+  <description>Apache Syncope Core AM Logic</description>
+  <groupId>org.apache.syncope.core.am</groupId>
+  <artifactId>syncope-core-am-logic</artifactId>
   <packaging>jar</packaging>
   
   <properties>
-    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
   </properties>
 
   <dependencies>
     <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-core</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>connector-framework</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>connector-framework-internal</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>slf4j-logging</artifactId>
-    </dependency>
-      
-    <dependency>
-      <groupId>org.apache.syncope.common.idm</groupId>
-      <artifactId>syncope-common-idm-lib</artifactId>
+      <groupId>org.apache.syncope.core.idrepo</groupId>
+      <artifactId>syncope-core-idrepo-logic</artifactId>
       <version>${project.version}</version>
     </dependency>
-    
-    <!-- TEST -->
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-simple</artifactId>
-      <version>${slf4j.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter</artifactId>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 
   <build>
+    <resources>
+      <resource>
+        <directory>${basedir}/src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+        
     <plugins>
       <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>buildnumber-maven-plugin</artifactId>
+        <inherited>true</inherited>
+        <configuration>
+          <doCheck>false</doCheck>
+          <doUpdate>false</doUpdate>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>validate</phase>
+            <goals>
+              <goal>create</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java
new file mode 100644
index 0000000..3d57c17
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java
@@ -0,0 +1,132 @@
+/*
+ * 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.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.core.persistence.api.dao.GatewayRouteDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.provisioning.api.data.GatewayRouteDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GatewayRouteLogic extends AbstractTransactionalLogic<GatewayRouteTO> {
+
+    @Autowired
+    private GatewayRouteDAO routeDAO;
+
+    @Autowired
+    private GatewayRouteDataBinder binder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    public List<GatewayRouteTO> list() {
+        return routeDAO.findAll().stream().map(binder::getGatewayRouteTO).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_CREATE + "')")
+    public GatewayRouteTO create(final GatewayRouteTO routeTO) {
+        GatewayRoute route = entityFactory.newEntity(GatewayRoute.class);
+        binder.getGatewayRoute(route, routeTO);
+
+        return binder.getGatewayRouteTO(routeDAO.save(route));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_READ + "')")
+    public GatewayRouteTO read(final String key) {
+        GatewayRoute route = routeDAO.find(key);
+        if (route == null) {
+            throw new NotFoundException("GatewayRoute " + key);
+        }
+        return binder.getGatewayRouteTO(route);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_UPDATE + "')")
+    public GatewayRouteTO update(final GatewayRouteTO routeTO) {
+        GatewayRoute route = routeDAO.find(routeTO.getKey());
+        if (route == null) {
+            throw new NotFoundException("GatewayRoute " + routeTO.getKey());
+        }
+
+        binder.getGatewayRoute(route, routeTO);
+
+        return binder.getGatewayRouteTO(routeDAO.save(route));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_DELETE + "')")
+    public GatewayRouteTO delete(final String key) {
+        GatewayRoute route = routeDAO.find(key);
+        if (route == null) {
+            throw new NotFoundException("GatewayRoute " + key);
+        }
+
+        GatewayRouteTO deleted = binder.getGatewayRouteTO(route);
+        routeDAO.delete(route);
+        return deleted;
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_PUSH + "')")
+    public void pushToSRA() {
+        // TODO
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_PUSH + "')")
+    public void pushToSRA(final String key) {
+        // TODO
+    }
+
+    @Override
+    protected GatewayRouteTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String key = null;
+
+        if (ArrayUtils.isNotEmpty(args) && ("create".equals(method.getName())
+                || "update".equals(method.getName())
+                || "delete".equals(method.getName()))) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof GatewayRouteTO) {
+                    key = ((GatewayRouteTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                return binder.getGatewayRouteTO(routeDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMEntitlementLoader.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMEntitlementLoader.java
new file mode 100644
index 0000000..2ca422c
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMEntitlementLoader.java
@@ -0,0 +1,38 @@
+/*
+ * 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.logic.init;
+
+import org.apache.syncope.common.lib.types.EntitlementsHolder;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AMEntitlementLoader implements SyncopeCoreLoader {
+
+    @Override
+    public int getOrder() {
+        return 900;
+    }
+
+    @Override
+    public void load() {
+        EntitlementsHolder.getInstance().addAll(AMEntitlement.values());
+    }
+}
diff --git a/core/am/pom.xml b/core/am/pom.xml
new file mode 100644
index 0000000..c430317
--- /dev/null
+++ b/core/am/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-core</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Core AM</name>
+  <description>Apache Syncope Core AM</description>
+  <groupId>org.apache.syncope.core</groupId>
+  <artifactId>syncope-core-am</artifactId>
+  <packaging>pom</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+  </properties>
+  
+  <modules>
+    <module>logic</module>
+    <module>rest-cxf</module>
+  </modules>
+</project>
diff --git a/core/persistence-api/pom.xml b/core/am/rest-cxf/pom.xml
similarity index 54%
copy from core/persistence-api/pom.xml
copy to core/am/rest-cxf/pom.xml
index c5a4add..80de056 100644
--- a/core/persistence-api/pom.xml
+++ b/core/am/rest-cxf/pom.xml
@@ -22,71 +22,65 @@ under the License.
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
-    <groupId>org.apache.syncope</groupId>
-    <artifactId>syncope-core</artifactId>
+    <groupId>org.apache.syncope.core</groupId>
+    <artifactId>syncope-core-am</artifactId>
     <version>3.0.0-SNAPSHOT</version>
   </parent>
 
-  <name>Apache Syncope Core Persistence API</name>
-  <description>Apache Syncope Core Persistence API</description>
-  <groupId>org.apache.syncope.core</groupId>
-  <artifactId>syncope-core-persistence-api</artifactId>
+  <name>Apache Syncope Core AM REST CXF</name>
+  <description>Apache Syncope Core AM REST CXF</description>
+  <groupId>org.apache.syncope.core.am</groupId>
+  <artifactId>syncope-core-am-rest-cxf</artifactId>
   <packaging>jar</packaging>
   
   <properties>
-    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
   </properties>
 
   <dependencies>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-core</artifactId>
+    <dependency> 
+      <groupId>javax.servlet</groupId> 
+      <artifactId>javax.servlet-api</artifactId> 
+      <scope>provided</scope>
     </dependency>
 
     <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>connector-framework</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>connector-framework-internal</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>net.tirasa.connid</groupId>
-      <artifactId>slf4j-logging</artifactId>
+      <groupId>org.apache.syncope.common.am</groupId>
+      <artifactId>syncope-common-am-rest-api</artifactId>
+      <version>${project.version}</version>
+      <classifier>javadoc</classifier>
     </dependency>
-      
+    
     <dependency>
-      <groupId>org.apache.syncope.common.idm</groupId>
-      <artifactId>syncope-common-idm-lib</artifactId>
+      <groupId>org.apache.syncope.core.idrepo</groupId>
+      <artifactId>syncope-core-idrepo-rest-cxf</artifactId>
       <version>${project.version}</version>
     </dependency>
-    
-    <!-- TEST -->
     <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-simple</artifactId>
-      <version>${slf4j.version}</version>
-      <scope>test</scope>
+      <groupId>org.apache.syncope.core.am</groupId>
+      <artifactId>syncope-core-am-logic</artifactId>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter</artifactId>
-      <scope>test</scope>
+      <groupId>org.apache.syncope.common.am</groupId>
+      <artifactId>syncope-common-am-rest-api</artifactId>
+      <version>${project.version}</version>
     </dependency>
   </dependencies>
 
   <build>
-    <plugins>
+    <plugins>            
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
     </plugins>
+
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
   </build>
 </project>
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java
new file mode 100644
index 0000000..f02f0c1
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java
@@ -0,0 +1,75 @@
+/*
+ * 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.service;
+
+import java.net.URI;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.GatewayRouteService;
+import org.apache.syncope.core.logic.GatewayRouteLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GatewayRouteServiceImpl extends AbstractServiceImpl implements GatewayRouteService {
+
+    @Autowired
+    private GatewayRouteLogic logic;
+    
+    @Override
+    public List<GatewayRouteTO> list() {
+        return logic.list();
+    }
+
+    @Override
+    public Response create(final GatewayRouteTO routeTO) {
+        GatewayRouteTO createdRoute = logic.create(routeTO);
+        URI location = uriInfo.getAbsolutePathBuilder().path(createdRoute.getKey()).build();
+        return Response.created(location).
+                header(RESTHeaders.RESOURCE_KEY, createdRoute.getKey()).
+                build();
+    }
+
+    @Override
+    public GatewayRouteTO read(final String key) {
+        return logic.read(key);
+    }
+
+    @Override
+    public void update(final GatewayRouteTO routeTO) {
+        logic.update(routeTO);
+    }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
+
+    @Override
+    public void pushToSRA() {
+        logic.pushToSRA();
+    }
+
+    @Override
+    public void pushToSRA(final String key) {
+        logic.pushToSRA(key);
+    }
+}
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicContext.java
index 475e2ef..2a6ed43 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicContext.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.logic;
 
+import java.lang.reflect.InvocationTargetException;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.springframework.context.EnvironmentAware;
 import org.springframework.context.annotation.Bean;
@@ -53,17 +54,19 @@ public class LogicContext implements EnvironmentAware {
 
     @Bean
     public LogicInvocationHandler logicInvocationHandler()
-            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException, 
+            NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
 
         return (LogicInvocationHandler) Class.forName(env.getProperty("logicInvocationHandler")).
-                newInstance();
+                getDeclaredConstructor().newInstance();
     }
 
     @Bean
     public ImplementationLookup classPathScanImplementationLookup()
-            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException, 
+            NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
 
         return (ImplementationLookup) Class.forName(env.getProperty("classPathScanImplementationLookup")).
-                newInstance();
+                getDeclaredConstructor().newInstance();
     }
 }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
index 36f6aa6..c942730 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
@@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
@@ -45,7 +46,7 @@ public class MemoryAppender extends AbstractAppender {
             final Filter filter,
             final boolean ignoreExceptions) {
 
-        super(name, filter, null, ignoreExceptions);
+        super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY);
         this.statements = new CircularFifoQueue<>(size);
     }
 
diff --git a/core/persistence-api/pom.xml b/core/persistence-api/pom.xml
index c5a4add..61a3643 100644
--- a/core/persistence-api/pom.xml
+++ b/core/persistence-api/pom.xml
@@ -66,6 +66,11 @@ under the License.
       <artifactId>syncope-common-idm-lib</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.am</groupId>
+      <artifactId>syncope-common-am-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     
     <!-- TEST -->
     <dependency>
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GatewayRouteDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GatewayRouteDAO.java
new file mode 100644
index 0000000..85dbfa4
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GatewayRouteDAO.java
@@ -0,0 +1,35 @@
+/*
+ * 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 java.util.List;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+
+public interface GatewayRouteDAO extends DAO<GatewayRoute> {
+
+    GatewayRoute find(String key);
+
+    List<GatewayRoute> findAll();
+
+    GatewayRoute save(GatewayRoute route);
+
+    void delete(String key);
+
+    void delete(GatewayRoute route);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java
new file mode 100644
index 0000000..70f1e6e
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java
@@ -0,0 +1,48 @@
+/*
+ * 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.net.URI;
+import java.util.List;
+import org.apache.syncope.common.lib.types.GatewayFilter;
+import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+
+public interface GatewayRoute extends Entity {
+
+    String getName();
+
+    void setName(String name);
+
+    URI getTarget();
+
+    void setTarget(URI target);
+
+    List<GatewayFilter> getFilters();
+
+    void setFilters(List<GatewayFilter> filters);
+
+    List<GatewayPredicate> getPredicates();
+
+    void setPredicates(List<GatewayPredicate> predicates);
+
+    GatewayRouteStatus getStatus();
+
+    void setStatus(GatewayRouteStatus status);
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGatewayRouteDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGatewayRouteDAO.java
new file mode 100644
index 0000000..4466ca9
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGatewayRouteDAO.java
@@ -0,0 +1,67 @@
+/*
+ * 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.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.api.dao.GatewayRouteDAO;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.persistence.jpa.entity.JPAGatewayRoute;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPAGatewayRouteDAO extends AbstractDAO<GatewayRoute> implements GatewayRouteDAO {
+
+    @Transactional(readOnly = true)
+    @Override
+    public GatewayRoute find(final String key) {
+        return entityManager().find(JPAGatewayRoute.class, key);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<GatewayRoute> findAll() {
+        TypedQuery<GatewayRoute> query = entityManager().createQuery(
+                "SELECT e FROM " + JPAGatewayRoute.class.getSimpleName() + " e", GatewayRoute.class);
+
+        return query.getResultList();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public GatewayRoute save(final GatewayRoute report) {
+        return entityManager().merge(report);
+    }
+
+    @Override
+    public void delete(final String key) {
+        GatewayRoute report = find(key);
+        if (report == null) {
+            return;
+        }
+
+        delete(report);
+    }
+
+    @Override
+    public void delete(final GatewayRoute report) {
+        entityManager().remove(report);
+    }
+}
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 81be309..9d46c27 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
@@ -130,6 +130,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnit;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Remediation;
@@ -300,6 +301,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPARemediation();
         } else if (reference.equals(Batch.class)) {
             result = (E) new JPABatch();
+        } else if (reference.equals(GatewayRoute.class)) {
+            result = (E) new JPAGatewayRoute();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java
new file mode 100644
index 0000000..8aee362
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java
@@ -0,0 +1,116 @@
+/*
+ * 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.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import org.apache.syncope.common.lib.types.GatewayFilter;
+import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.persistence.jpa.validation.entity.GatewayRouteCheck;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+@Entity
+@Table(name = JPAGatewayRoute.TABLE)
+@GatewayRouteCheck
+public class JPAGatewayRoute extends AbstractGeneratedKeyEntity implements GatewayRoute {
+
+    private static final long serialVersionUID = -8718852361106840530L;
+
+    public static final String TABLE = "GatewayRoute";
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    private String target;
+
+    @Lob
+    private String predicates;
+
+    @Lob
+    private String filters;
+
+    @NotNull
+    @Enumerated(EnumType.STRING)
+    private GatewayRouteStatus status;
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public URI getTarget() {
+        return URI.create(target);
+    }
+
+    @Override
+    public void setTarget(final URI target) {
+        this.target = target == null ? null : target.toASCIIString();
+    }
+
+    @Override
+    public List<GatewayFilter> getFilters() {
+        return filters == null
+                ? Collections.emptyList()
+                : Arrays.asList(POJOHelper.deserialize(filters, GatewayFilter[].class));
+    }
+
+    @Override
+    public void setFilters(final List<GatewayFilter> filters) {
+        this.filters = POJOHelper.serialize(filters);
+    }
+
+    @Override
+    public List<GatewayPredicate> getPredicates() {
+        return predicates == null
+                ? Collections.emptyList()
+                : Arrays.asList(POJOHelper.deserialize(predicates, GatewayPredicate[].class));
+    }
+
+    @Override
+    public void setPredicates(final List<GatewayPredicate> predicates) {
+        this.predicates = POJOHelper.serialize(predicates);
+    }
+
+    @Override
+    public GatewayRouteStatus getStatus() {
+        return status;
+    }
+
+    @Override
+    public void setStatus(final GatewayRouteStatus status) {
+        this.status = status;
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteCheck.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteCheck.java
new file mode 100644
index 0000000..1735d2d
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteCheck.java
@@ -0,0 +1,41 @@
+/*
+ * 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.validation.entity;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = GatewayRouteValidator.class)
+@Documented
+public @interface GatewayRouteCheck {
+
+    String message() default "{org.apache.syncope.core.persistence.validation.gatewayroute}";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteValidator.java
new file mode 100644
index 0000000..490528f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/GatewayRouteValidator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.validation.entity;
+
+import javax.validation.ConstraintValidatorContext;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+
+public class GatewayRouteValidator extends AbstractValidator<GatewayRouteCheck, GatewayRoute> {
+
+    @Override
+    public boolean isValid(final GatewayRoute route, final ConstraintValidatorContext context) {
+        context.disableDefaultConstraintViolation();
+
+        boolean isValid = true;
+
+        if (isHtml(route.getName())) {
+            context.buildConstraintViolationWithTemplate(
+                    getTemplate(EntityViolationType.InvalidName, "Invalid name")).
+                    addPropertyNode("name").addConstraintViolation();
+
+            isValid = false;
+        }
+
+        if (route.getPredicates().size() > 1) {
+            if (route.getPredicates().stream().allMatch(predicate -> predicate.getCond() != null)) {
+                context.buildConstraintViolationWithTemplate(
+                        getTemplate(EntityViolationType.InvalidValueList,
+                                "Cond must be set when predicates are more than one")).
+                        addPropertyNode("predicates").addConstraintViolation();
+            }
+
+            isValid = false;
+        }
+
+        return isValid;
+    }
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java
new file mode 100644
index 0000000..2bcc959
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.inner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import javax.ws.rs.HttpMethod;
+import org.apache.syncope.common.lib.types.FilterFactory;
+import org.apache.syncope.common.lib.types.GatewayFilter;
+import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+import org.apache.syncope.common.lib.types.PredicateFactory;
+import org.apache.syncope.core.persistence.api.dao.GatewayRouteDAO;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class GatewayRouteTest extends AbstractTest {
+
+    @Autowired
+    private GatewayRouteDAO routeDAO;
+
+    @Test
+    public void find() {
+        GatewayRoute route = routeDAO.find("ec7bada2-3dd6-460c-8441-65521d005ffa");
+        assertNotNull(route);
+        assertEquals(GatewayRouteStatus.PUBLISHED, route.getStatus());
+        assertEquals(1, route.getPredicates().size());
+
+        route = routeDAO.find(UUID.randomUUID().toString());
+        assertNull(route);
+    }
+
+    @Test
+    public void findAll() {
+        List<GatewayRoute> routes = routeDAO.findAll();
+        assertNotNull(routes);
+        assertEquals(1, routes.size());
+    }
+
+    @Test
+    public void save() {
+        GatewayRoute route = entityFactory.newEntity(GatewayRoute.class);
+        route.setName("just for test");
+        route.setTarget(URI.create("http://httpbin.org:80"));
+        route.setPredicates(Arrays.asList(new GatewayPredicate.Builder().
+                factory(PredicateFactory.METHOD).args(HttpMethod.GET).build()));
+        route.setFilters(Arrays.asList(new GatewayFilter.Builder().
+                factory(FilterFactory.ADD_REQUEST_HEADER).args("X-Request-Foo, Bar").build()));
+        route.setStatus(GatewayRouteStatus.DRAFT);
+
+        int beforeCount = routeDAO.findAll().size();
+
+        route = routeDAO.save(route);
+        assertNotNull(route);
+        assertNotNull(route.getKey());
+
+        int afterCount = routeDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+    }
+
+    @Test
+    public void delete() {
+        GatewayRoute route = routeDAO.find("ec7bada2-3dd6-460c-8441-65521d005ffa");
+        assertNotNull(route);
+
+        routeDAO.delete("ec7bada2-3dd6-460c-8441-65521d005ffa");
+
+        route = routeDAO.find("ec7bada2-3dd6-460c-8441-65521d005ffa");
+        assertNull(route);
+    }
+}
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index fe0077d..4bf6a5e 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -2558,4 +2558,7 @@ $$ }&#10;
   <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
   
   <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
+
+  <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
+                predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
 </dataset>
diff --git a/core/pom.xml b/core/pom.xml
index aed7643..27d3585 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -79,6 +79,7 @@ under the License.
   <modules>
     <module>idrepo</module>
     <module>idm</module>
+    <module>am</module>
     <module>persistence-api</module>
     <module>persistence-jpa</module>
     <module>persistence-jpa-json</module>
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GatewayRouteDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GatewayRouteDataBinder.java
new file mode 100644
index 0000000..d879f71
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GatewayRouteDataBinder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.api.data;
+
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+
+public interface GatewayRouteDataBinder {
+
+    void getGatewayRoute(GatewayRoute report, GatewayRouteTO reportTO);
+
+    GatewayRouteTO getGatewayRouteTO(GatewayRoute report);
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java
new file mode 100644
index 0000000..ac7e563
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.data;
+
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.provisioning.api.data.GatewayRouteDataBinder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GatewayRouteDataBinderImpl implements GatewayRouteDataBinder {
+
+    @Override
+    public void getGatewayRoute(final GatewayRoute route, final GatewayRouteTO routeTO) {
+        route.setName(routeTO.getName());
+        route.setTarget(routeTO.getTarget());
+        route.setFilters(routeTO.getFilters());
+        route.setPredicates(routeTO.getPredicates());
+        route.setStatus(routeTO.getStatus());
+    }
+
+    @Override
+    public GatewayRouteTO getGatewayRouteTO(final GatewayRoute route) {
+        GatewayRouteTO routeTO = new GatewayRouteTO();
+        routeTO.setKey(route.getKey());
+        routeTO.setName(route.getName());
+        routeTO.setTarget(route.getTarget());
+        routeTO.getFilters().addAll(route.getFilters());
+        routeTO.getPredicates().addAll(route.getPredicates());
+        routeTO.setStatus(route.getStatus());
+
+        return routeTO;
+    }
+}
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 5a156c9..422ed24 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -51,6 +51,11 @@ under the License.
       <artifactId>syncope-core-idm-rest-cxf</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.core.am</groupId>
+      <artifactId>syncope-core-am-rest-cxf</artifactId>
+      <version>${project.version}</version>
+    </dependency>
 
     <!-- TEST -->
     <dependency>
@@ -72,6 +77,12 @@ under the License.
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.syncope.client.am</groupId>
+      <artifactId>syncope-client-am-lib</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.apache.syncope.client.idm</groupId>
       <artifactId>syncope-client-idm-console</artifactId>
       <version>${project.version}</version>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 1e75b55..adcb34e 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -119,6 +119,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.common.rest.api.service.BpmnProcessService;
+import org.apache.syncope.common.rest.api.service.GatewayRouteService;
 import org.apache.syncope.common.rest.api.service.UserWorkflowTaskService;
 
 public abstract class AbstractITCase {
@@ -265,6 +266,8 @@ public abstract class AbstractITCase {
 
     protected static RemediationService remediationService;
 
+    protected static GatewayRouteService gatewayRouteService;
+
     protected static CamelRouteService camelRouteService;
 
     protected static SAML2SPService saml2SpService;
@@ -343,6 +346,7 @@ public abstract class AbstractITCase {
         securityQuestionService = adminClient.getService(SecurityQuestionService.class);
         implementationService = adminClient.getService(ImplementationService.class);
         remediationService = adminClient.getService(RemediationService.class);
+        gatewayRouteService = adminClient.getService(GatewayRouteService.class);
         camelRouteService = adminClient.getService(CamelRouteService.class);
         saml2SpService = adminClient.getService(SAML2SPService.class);
         saml2IdPService = adminClient.getService(SAML2IdPService.class);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java
new file mode 100644
index 0000000..5d4f8c4
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java
@@ -0,0 +1,106 @@
+/*
+ * 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.fit.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.net.URI;
+import java.util.List;
+import java.util.UUID;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.FilterFactory;
+import org.apache.syncope.common.lib.types.GatewayFilter;
+import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+import org.apache.syncope.common.lib.types.PredicateFactory;
+import org.apache.syncope.common.rest.api.service.GatewayRouteService;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+
+public class GatewayRouteITCase extends AbstractITCase {
+
+    @Test
+    public void read() {
+        GatewayRouteTO route = gatewayRouteService.read("ec7bada2-3dd6-460c-8441-65521d005ffa");
+        assertNotNull(route);
+        assertEquals(GatewayRouteStatus.PUBLISHED, route.getStatus());
+        assertEquals(1, route.getPredicates().size());
+
+        try {
+            gatewayRouteService.read(UUID.randomUUID().toString());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+    }
+
+    @Test
+    public void findAll() {
+        List<GatewayRouteTO> routes = gatewayRouteService.list();
+        assertNotNull(routes);
+        assertFalse(routes.isEmpty());
+    }
+
+    @Test
+    public void createUpdateDelete() {
+        GatewayRouteTO route = new GatewayRouteTO();
+        route.setName("just for test");
+        route.setTarget(URI.create("http://httpbin.org:80"));
+        route.getPredicates().add(new GatewayPredicate.Builder().
+                factory(PredicateFactory.METHOD).args(HttpMethod.GET).build());
+        route.getFilters().add(new GatewayFilter.Builder().
+                factory(FilterFactory.ADD_REQUEST_HEADER).args("X-Request-Foo, Bar").build());
+        route.setStatus(GatewayRouteStatus.DRAFT);
+
+        int beforeCount = gatewayRouteService.list().size();
+
+        Response response = gatewayRouteService.create(route);
+        assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode());
+        route = getObject(response.getLocation(), GatewayRouteService.class, GatewayRouteTO.class);
+        assertNotNull(route);
+        assertNotNull(route.getKey());
+
+        int afterCount = gatewayRouteService.list().size();
+        assertEquals(afterCount, beforeCount + 1);
+
+        route.setStatus(GatewayRouteStatus.STAGING);
+        gatewayRouteService.update(route);
+        route = gatewayRouteService.read(route.getKey());
+        assertEquals(GatewayRouteStatus.STAGING, route.getStatus());
+
+        gatewayRouteService.delete(route.getKey());
+
+        try {
+            gatewayRouteService.read(route.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+
+        int endCount = gatewayRouteService.list().size();
+        assertEquals(endCount, beforeCount);
+    }
+}
diff --git a/sra/src/main/resources/application.properties b/sra/src/main/resources/application.properties
index 5a56388..cc8c14b 100644
--- a/sra/src/main/resources/application.properties
+++ b/sra/src/main/resources/application.properties
@@ -19,3 +19,12 @@ spring.groovy.template.check-template-location=false
 spring.main.banner-mode=log
 
 server.port=8080
+
+management.endpoint.gateway.enabled=true
+management.endpoints.web.exposure.include=gateway
+
+spring.cloud.gateway.metrics.enabled=true
+management.endpoint.metrics.enabled=true
+management.endpoints.web.exposure.include=*
+management.endpoint.prometheus.enabled=true
+management.metrics.export.prometheus.enabled=true
diff --git a/sra/src/main/resources/log4j2.xml b/sra/src/main/resources/log4j2.xml
index 59d9264..d7211f5 100644
--- a/sra/src/main/resources/log4j2.xml
+++ b/sra/src/main/resources/log4j2.xml
@@ -22,7 +22,7 @@ under the License.
   <appenders>
 
     <RollingRandomAccessFile name="main" fileName="${log.directory}/sra.log"
-                             filePattern="${log.directory}/console-%d{yyyy-MM-dd}.log.gz"
+                             filePattern="${log.directory}/sra-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
         <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
@@ -33,6 +33,18 @@ under the License.
       </Policies>
     </RollingRandomAccessFile>
     
+    <RollingRandomAccessFile name="access" fileName="${log.directory}/sra-access.log"
+                             filePattern="${log.directory}/sra-access-%d{yyyy-MM-dd}.log.gz"
+                             immediateFlush="false" append="true">
+      <PatternLayout>
+        <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+      </PatternLayout>
+      <Policies>
+        <TimeBasedTriggeringPolicy/>
+        <SizeBasedTriggeringPolicy size="250 MB"/>
+      </Policies>
+    </RollingRandomAccessFile>
+
   </appenders>
 
   <loggers>
@@ -44,7 +56,12 @@ under the License.
     <asyncLogger name="org.apache.syncope.sra" additivity="false" level="INFO">
       <appender-ref ref="main"/>
     </asyncLogger>
-    
+
+    <!-- Requires -Dreactor.netty.http.server.accessLogEnabled=true to work-->
+    <asyncLogger name="reactor.netty.http.server.AccessLog" additivity="false" level="INFO">
+      <appender-ref ref="access"/>
+    </asyncLogger>
+
     <root level="INFO">
       <appender-ref ref="main"/>
     </root>