http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
----------------------------------------------------------------------
diff --cc 
gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
index a87c8d0,0000000..fe39f25
mode 100644,000000..100644
--- 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
+++ 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jDispatcherFilter.java
@@@ -1,215 -1,0 +1,214 @@@
 +/**
 + * 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.knox.gateway.pac4j.filter;
 +
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.knox.gateway.pac4j.Pac4jMessages;
 +import org.apache.knox.gateway.pac4j.session.KnoxSessionStore;
 +import org.apache.knox.gateway.services.GatewayServices;
 +import org.apache.knox.gateway.services.security.KeystoreService;
 +import org.apache.knox.gateway.services.security.MasterService;
 +import org.apache.knox.gateway.services.security.AliasService;
 +import org.apache.knox.gateway.services.security.AliasServiceException;
 +import org.apache.knox.gateway.services.security.CryptoService;
 +import org.pac4j.config.client.PropertiesConfigFactory;
 +import org.pac4j.core.client.Client;
 +import org.pac4j.core.config.Config;
 +import org.pac4j.core.config.ConfigSingleton;
 +import org.pac4j.core.context.J2EContext;
- import org.pac4j.core.context.Pac4jConstants;
 +import org.pac4j.core.util.CommonHelper;
 +import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
 +import 
org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
 +import org.pac4j.j2e.filter.CallbackFilter;
- import org.pac4j.j2e.filter.RequiresAuthenticationFilter;
++import org.pac4j.j2e.filter.SecurityFilter;
 +
 +import javax.servlet.*;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import java.io.IOException;
 +import java.util.Enumeration;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +/**
 + * <p>This is the main filter for the pac4j provider. The pac4j provider 
module heavily relies on the j2e-pac4j library 
(https://github.com/pac4j/j2e-pac4j).</p>
 + * <p>This filter dispatches the HTTP calls between the j2e-pac4j filters:</p>
 + * <ul>
 + *     <li>to the {@link CallbackFilter} if the <code>client_name</code> 
parameter exists: it finishes the authentication process</li>
 + *     <li>to the {@link RequiresAuthenticationFilter} otherwise: it starts 
the authentication process (redirection to the identity provider) if the user 
is not authenticated</li>
 + * </ul>
 + * <p>It uses the {@link KnoxSessionStore} to manage session data. The 
generated cookies are defined on a domain name
 + * which can be configured via the domain suffix parameter: 
<code>pac4j.cookie.domain.suffix</code>.</p>
 + * <p>The callback url must be defined to the current protected url (KnoxSSO 
service for example) via the parameter: <code>pac4j.callbackUrl</code>.</p>
 + *
 + * @since 0.8.0
 + */
 +public class Pac4jDispatcherFilter implements Filter {
 +
 +  private static Pac4jMessages log = MessagesFactory.get(Pac4jMessages.class);
 +
 +  public static final String TEST_BASIC_AUTH = "testBasicAuth";
 +
 +  public static final String PAC4J_CALLBACK_URL = "pac4j.callbackUrl";
 +
 +  public static final String PAC4J_CALLBACK_PARAMETER = "pac4jCallback";
 +
 +  private static final String PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM = 
"pac4j.cookie.domain.suffix";
 +
 +  private CallbackFilter callbackFilter;
 +
-   private RequiresAuthenticationFilter requiresAuthenticationFilter;
++  private SecurityFilter securityFilter;
 +  private MasterService masterService = null;
 +  private KeystoreService keystoreService = null;
 +  private AliasService aliasService = null;
 +
 +  @Override
 +  public void init( FilterConfig filterConfig ) throws ServletException {
 +    // JWT service
 +    final ServletContext context = filterConfig.getServletContext();
 +    CryptoService cryptoService = null;
 +    String clusterName = null;
 +    if (context != null) {
 +      GatewayServices services = (GatewayServices) 
context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
 +      clusterName = (String) 
context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
 +      if (services != null) {
 +        keystoreService = (KeystoreService) 
services.getService(GatewayServices.KEYSTORE_SERVICE);
 +        cryptoService = (CryptoService) 
services.getService(GatewayServices.CRYPTO_SERVICE);
 +        aliasService = (AliasService) 
services.getService(GatewayServices.ALIAS_SERVICE);
 +        masterService = (MasterService) services.getService("MasterService");
 +      }
 +    }
 +    // crypto service, alias service and cluster name are mandatory
 +    if (cryptoService == null || aliasService == null || clusterName == null) 
{
 +      log.cryptoServiceAndAliasServiceAndClusterNameRequired();
 +      throw new ServletException("The crypto service, alias service and 
cluster name are required.");
 +    }
 +    try {
 +      aliasService.getPasswordFromAliasForCluster(clusterName, 
KnoxSessionStore.PAC4J_PASSWORD, true);
 +    } catch (AliasServiceException e) {
 +      log.unableToGenerateAPasswordForEncryption(e);
 +      throw new ServletException("Unable to generate a password for 
encryption.");
 +    }
 +
 +    // url to SSO authentication provider
 +    String pac4jCallbackUrl = 
filterConfig.getInitParameter(PAC4J_CALLBACK_URL);
 +    if (pac4jCallbackUrl == null) {
 +      log.ssoAuthenticationProviderUrlRequired();
 +      throw new ServletException("Required pac4j callback URL is missing.");
 +    }
 +    // add the callback parameter to know it's a callback
 +    pac4jCallbackUrl = CommonHelper.addParameter(pac4jCallbackUrl, 
PAC4J_CALLBACK_PARAMETER, "true");
 +
 +    final Config config;
 +    final String clientName;
 +    // client name from servlet parameter (mandatory)
-     final String clientNameParameter = 
filterConfig.getInitParameter(Pac4jConstants.CLIENT_NAME);
++    final String clientNameParameter = 
filterConfig.getInitParameter("clientName");
 +    if (clientNameParameter == null) {
 +      log.clientNameParameterRequired();
 +      throw new ServletException("Required pac4j clientName parameter is 
missing.");
 +    }
 +    if (TEST_BASIC_AUTH.equalsIgnoreCase(clientNameParameter)) {
 +      // test configuration
 +      final IndirectBasicAuthClient indirectBasicAuthClient = new 
IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
 +      indirectBasicAuthClient.setRealmName("Knox TEST");
 +      config = new Config(pac4jCallbackUrl, indirectBasicAuthClient);
 +      clientName = "IndirectBasicAuthClient";
 +    } else {
 +      // get clients from the init parameters
 +      final Map<String, String> properties = new HashMap<>();
 +      final Enumeration<String> names = filterConfig.getInitParameterNames();
 +      addDefaultConfig(clientNameParameter, properties);
 +      while (names.hasMoreElements()) {
 +        final String key = names.nextElement();
 +        properties.put(key, filterConfig.getInitParameter(key));
 +      }
 +      final PropertiesConfigFactory propertiesConfigFactory = new 
PropertiesConfigFactory(pac4jCallbackUrl, properties);
 +      config = propertiesConfigFactory.build();
 +      final List<Client> clients = config.getClients().getClients();
 +      if (clients == null || clients.size() == 0) {
 +        log.atLeastOnePac4jClientMustBeDefined();
 +        throw new ServletException("At least one pac4j client must be 
defined.");
 +      }
 +      if (CommonHelper.isBlank(clientNameParameter)) {
 +        clientName = clients.get(0).getName();
 +      } else {
 +        clientName = clientNameParameter;
 +      }
 +    }
 +
 +    callbackFilter = new CallbackFilter();
-     requiresAuthenticationFilter = new RequiresAuthenticationFilter();
-     requiresAuthenticationFilter.setClientName(clientName);
-     requiresAuthenticationFilter.setConfig(config);
++    securityFilter = new SecurityFilter();
++    securityFilter.setClients(clientName);
++    securityFilter.setConfig(config);
 +
 +    final String domainSuffix = 
filterConfig.getInitParameter(PAC4J_COOKIE_DOMAIN_SUFFIX_PARAM);
 +    config.setSessionStore(new KnoxSessionStore(cryptoService, clusterName, 
domainSuffix));
 +    ConfigSingleton.setConfig(config);
 +  }
 +
 +  private void addDefaultConfig(String clientNameParameter, Map<String, 
String> properties) {
 +    // add default saml params
 +    if (clientNameParameter.contains("SAML2Client")) {
 +      properties.put(PropertiesConfigFactory.SAML_KEYSTORE_PATH,
 +          keystoreService.getKeystorePath());
 +
 +      properties.put(PropertiesConfigFactory.SAML_KEYSTORE_PASSWORD,
 +          new String(masterService.getMasterSecret()));
 +
 +      // check for provisioned alias for private key
 +      char[] gip = null;
 +      try {
 +        gip = aliasService.getGatewayIdentityPassphrase();
 +      }
 +      catch(AliasServiceException ase) {
 +        log.noPrivateKeyPasshraseProvisioned(ase);
 +      }
 +      if (gip != null) {
 +        properties.put(PropertiesConfigFactory.SAML_PRIVATE_KEY_PASSWORD,
 +            new String(gip));
 +      }
 +      else {
 +        // no alias provisioned then use the master
 +        properties.put(PropertiesConfigFactory.SAML_PRIVATE_KEY_PASSWORD,
 +            new String(masterService.getMasterSecret()));
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public void doFilter( ServletRequest servletRequest, ServletResponse 
servletResponse, FilterChain filterChain) throws IOException, ServletException {
 +
 +    final HttpServletRequest request = (HttpServletRequest) servletRequest;
 +    final HttpServletResponse response = (HttpServletResponse) 
servletResponse;
 +    final J2EContext context = new J2EContext(request, response, 
ConfigSingleton.getConfig().getSessionStore());
 +
 +    // it's a callback from an identity provider
 +    if (request.getParameter(PAC4J_CALLBACK_PARAMETER) != null) {
 +      // apply CallbackFilter
 +      callbackFilter.doFilter(servletRequest, servletResponse, filterChain);
 +    } else {
 +      // otherwise just apply security and requires authentication
 +      // apply RequiresAuthenticationFilter
-       requiresAuthenticationFilter.doFilter(servletRequest, servletResponse, 
filterChain);
++      securityFilter.doFilter(servletRequest, servletResponse, filterChain);
 +    }
 +  }
 +
 +  @Override
 +  public void destroy() { }
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jIdentityAdapter.java
----------------------------------------------------------------------
diff --cc 
gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jIdentityAdapter.java
index 90395f1,0000000..6387a0b
mode 100644,000000..100644
--- 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jIdentityAdapter.java
+++ 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/filter/Pac4jIdentityAdapter.java
@@@ -1,142 -1,0 +1,146 @@@
 +/**
 + * 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.knox.gateway.pac4j.filter;
 +
 +import org.apache.knox.gateway.audit.api.Action;
 +import org.apache.knox.gateway.audit.api.ActionOutcome;
 +import org.apache.knox.gateway.audit.api.AuditService;
 +import org.apache.knox.gateway.audit.api.AuditServiceFactory;
 +import org.apache.knox.gateway.audit.api.Auditor;
 +import org.apache.knox.gateway.audit.api.ResourceType;
 +import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
 +import org.apache.knox.gateway.filter.AbstractGatewayFilter;
 +import org.apache.knox.gateway.security.PrimaryPrincipal;
 +import org.pac4j.core.config.ConfigSingleton;
 +import org.pac4j.core.context.J2EContext;
++import org.pac4j.core.profile.CommonProfile;
 +import org.pac4j.core.profile.ProfileManager;
- import org.pac4j.core.profile.UserProfile;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +import javax.security.auth.Subject;
 +import javax.servlet.Filter;
 +import javax.servlet.FilterChain;
 +import javax.servlet.FilterConfig;
 +import javax.servlet.ServletException;
 +import javax.servlet.ServletRequest;
 +import javax.servlet.ServletResponse;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import java.io.IOException;
 +import java.security.PrivilegedActionException;
 +import java.security.PrivilegedExceptionAction;
++import java.util.Optional;
 +
 +/**
 + * <p>This filter retrieves the authenticated user saved by the pac4j 
provider and injects it into the J2E HTTP request.</p>
 + *
 + * @since 0.8.0
 + */
 +public class Pac4jIdentityAdapter implements Filter {
 +
 +  private static final Logger logger = 
LoggerFactory.getLogger(Pac4jIdentityAdapter.class);
 +
 +  private static AuditService auditService = 
AuditServiceFactory.getAuditService();
 +  private static Auditor auditor = auditService.getAuditor(
 +      AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
 +      AuditConstants.KNOX_COMPONENT_NAME );
 +
 +  private String testIdentifier;
 +
 +  @Override
 +  public void init( FilterConfig filterConfig ) throws ServletException {
 +  }
 +
 +  public void destroy() {
 +  }
 +
 +  public void doFilter(ServletRequest servletRequest, ServletResponse 
servletResponse, FilterChain chain)
 +      throws IOException, ServletException {
 +
 +    final HttpServletRequest request = (HttpServletRequest) servletRequest;
 +    final HttpServletResponse response = (HttpServletResponse) 
servletResponse;
 +    final J2EContext context = new J2EContext(request, response, 
ConfigSingleton.getConfig().getSessionStore());
-     final ProfileManager manager = new ProfileManager(context);
-     final UserProfile profile = manager.get(true);
-     logger.debug("User authenticated as: {}", profile);
-     manager.remove(true);
-     final String id = profile.getId();
-     testIdentifier = id;
-     PrimaryPrincipal pp = new PrimaryPrincipal(id);
-     Subject subject = new Subject();
-     subject.getPrincipals().add(pp);
-     auditService.getContext().setUsername(id);
-     String sourceUri = (String)request.getAttribute( 
AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
-     auditor.audit(Action.AUTHENTICATION, sourceUri, ResourceType.URI, 
ActionOutcome.SUCCESS);
-     
-     doAs(request, response, chain, subject);
++    final ProfileManager<CommonProfile> manager = new 
ProfileManager<CommonProfile>(context);
++    final Optional<CommonProfile> optional = manager.get(true);
++    if (optional.isPresent()) {
++      CommonProfile profile = optional.get();
++      logger.debug("User authenticated as: {}", profile);
++      manager.remove(true);
++      final String id = profile.getId();
++      testIdentifier = id;
++      PrimaryPrincipal pp = new PrimaryPrincipal(id);
++      Subject subject = new Subject();
++      subject.getPrincipals().add(pp);
++      auditService.getContext().setUsername(id);
++      String sourceUri = (String)request.getAttribute( 
AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
++      auditor.audit(Action.AUTHENTICATION, sourceUri, ResourceType.URI, 
ActionOutcome.SUCCESS);
++
++      doAs(request, response, chain, subject);
++    }
 +  }
-   
++
 +  private void doAs(final ServletRequest request,
 +      final ServletResponse response, final FilterChain chain, Subject 
subject)
 +      throws IOException, ServletException {
 +    try {
 +      Subject.doAs(
 +          subject,
 +          new PrivilegedExceptionAction<Object>() {
 +            public Object run() throws Exception {
 +              chain.doFilter(request, response);
 +              return null;
 +            }
 +          }
 +          );
 +    }
 +    catch (PrivilegedActionException e) {
 +      Throwable t = e.getCause();
 +      if (t instanceof IOException) {
 +        throw (IOException) t;
 +      }
 +      else if (t instanceof ServletException) {
 +        throw (ServletException) t;
 +      }
 +      else {
 +        throw new ServletException(t);
 +      }
 +    }
 +  }
 +
 +  /**
 +   * For tests only.
 +   */
 +  public static void setAuditService(AuditService auditService) {
 +    Pac4jIdentityAdapter.auditService = auditService;
 +  }
 +
 +  /**
 +   * For tests only.
 +   */
 +  public static void setAuditor(Auditor auditor) {
 +    Pac4jIdentityAdapter.auditor = auditor;
 +  }
 +
 +  /**
 +   * For tests only.
 +     */
 +  public String getTestIdentifier() {
 +    return testIdentifier;
 +  }
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
----------------------------------------------------------------------
diff --cc 
gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
index 6ce002c,0000000..4ba55ea
mode 100644,000000..100644
--- 
a/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
+++ 
b/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java
@@@ -1,120 -1,0 +1,146 @@@
 +/**
 + * 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.knox.gateway.pac4j.session;
 +
 +import org.apache.commons.codec.binary.Base64;
 +import org.apache.knox.gateway.services.security.CryptoService;
 +import org.apache.knox.gateway.services.security.EncryptionResult;
 +import org.apache.knox.gateway.util.Urls;
 +import org.pac4j.core.context.ContextHelper;
 +import org.pac4j.core.context.Cookie;
 +import org.pac4j.core.context.WebContext;
 +import org.pac4j.core.context.session.SessionStore;
 +import org.pac4j.core.exception.TechnicalException;
 +import org.pac4j.core.util.JavaSerializationHelper;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +import java.io.Serializable;
++import java.util.Map;
 +
 +/**
 + * Specific session store where data are saved into cookies (and not in 
memory).
 + * Each data is encrypted and base64 encoded before being saved as a cookie 
(for security reasons).
 + *
 + * @since 0.8.0
 + */
 +public class KnoxSessionStore implements SessionStore {
 +
 +    private static final Logger logger = 
LoggerFactory.getLogger(KnoxSessionStore.class);
 +
 +    public static final String PAC4J_PASSWORD = "pac4j.password";
 +
 +    public static final String PAC4J_SESSION_PREFIX = "pac4j.session.";
 +
 +    private final JavaSerializationHelper javaSerializationHelper;
 +
 +    private final CryptoService cryptoService;
 +
 +    private final String clusterName;
 +
 +    private final String domainSuffix;
 +
 +    public KnoxSessionStore(final CryptoService cryptoService, final String 
clusterName, final String domainSuffix) {
 +        javaSerializationHelper = new JavaSerializationHelper();
 +        this.cryptoService = cryptoService;
 +        this.clusterName = clusterName;
 +        this.domainSuffix = domainSuffix;
 +    }
 +
 +    public String getOrCreateSessionId(WebContext context) {
 +        return null;
 +    }
 +
 +    private Serializable decryptBase64(final String v) {
 +        if (v != null && v.length() > 0) {
 +            byte[] bytes = Base64.decodeBase64(v);
 +            EncryptionResult result = EncryptionResult.fromByteArray(bytes);
 +            byte[] clear = cryptoService.decryptForCluster(this.clusterName,
 +                    PAC4J_PASSWORD,
 +                    result.cipher,
 +                    result.iv,
 +                    result.salt);
 +            if (clear != null) {
 +                return javaSerializationHelper.unserializeFromBytes(clear);
 +            }
 +        }
 +        return null;
 +    }
 +
 +    public Object get(WebContext context, String key) {
 +        final Cookie cookie = ContextHelper.getCookie(context, 
PAC4J_SESSION_PREFIX + key);
 +        Object value = null;
 +        if (cookie != null) {
 +            value = decryptBase64(cookie.getValue());
 +        }
 +        logger.debug("Get from session: {} = {}", key, value);
 +        return value;
 +    }
 +
 +    private String encryptBase64(final Object o) {
-         if (o == null || o.equals("")) {
++        if (o == null || o.equals("")
++            || (o instanceof Map<?,?> && ((Map<?,?>)o).isEmpty())) {
 +            return null;
 +        } else {
 +            final byte[] bytes = 
javaSerializationHelper.serializeToBytes((Serializable) o);
 +            EncryptionResult result = 
cryptoService.encryptForCluster(this.clusterName, PAC4J_PASSWORD, bytes);
 +            return Base64.encodeBase64String(result.toByteAray());
 +        }
 +    }
 +
 +    public void set(WebContext context, String key, Object value) {
 +        logger.debug("Save in session: {} = {}", key, value);
 +        final Cookie cookie = new Cookie(PAC4J_SESSION_PREFIX + key, 
encryptBase64(value));
 +        try {
 +            String domain = Urls.getDomainName(context.getFullRequestURL(), 
this.domainSuffix);
 +            if (domain == null) {
 +                domain = context.getServerName();
 +            }
 +            cookie.setDomain(domain);
 +        } catch (final Exception e) {
 +            throw new TechnicalException(e);
 +        }
 +        cookie.setHttpOnly(true);
 +        cookie.setSecure(ContextHelper.isHttpsOrSecure(context));
 +        context.addResponseCookie(cookie);
 +    }
++
++    @Override
++    public SessionStore buildFromTrackableSession(WebContext arg0, Object 
arg1) {
++        // TODO Auto-generated method stub
++        return null;
++    }
++
++    @Override
++    public boolean destroySession(WebContext arg0) {
++        // TODO Auto-generated method stub
++        return false;
++    }
++
++    @Override
++    public Object getTrackableSession(WebContext arg0) {
++        // TODO Auto-generated method stub
++        return null;
++    }
++
++    @Override
++    public boolean renewSession(WebContext arg0) {
++        // TODO Auto-generated method stub
++        return false;
++    }
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderTest.java
----------------------------------------------------------------------
diff --cc 
gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderTest.java
index 606d042,0000000..e4e0462
mode 100644,000000..100644
--- 
a/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderTest.java
+++ 
b/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderTest.java
@@@ -1,150 -1,0 +1,150 @@@
 +/**
 + * 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.knox.gateway.pac4j;
 +
 +import org.apache.knox.gateway.audit.api.AuditContext;
 +import org.apache.knox.gateway.audit.api.AuditService;
 +import org.apache.knox.gateway.audit.api.Auditor;
 +import org.apache.knox.gateway.pac4j.filter.Pac4jDispatcherFilter;
 +import org.apache.knox.gateway.pac4j.filter.Pac4jIdentityAdapter;
 +import org.apache.knox.gateway.pac4j.session.KnoxSessionStore;
 +import org.apache.knox.gateway.services.GatewayServices;
 +import org.apache.knox.gateway.services.security.AliasService;
 +import org.apache.knox.gateway.services.security.impl.DefaultCryptoService;
 +import org.junit.Test;
 +import org.pac4j.core.client.Clients;
 +import org.pac4j.core.context.Pac4jConstants;
 +import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
 +
 +import javax.servlet.*;
 +import javax.servlet.http.*;
 +
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +import static org.mockito.Mockito.*;
 +import static org.junit.Assert.*;
 +
 +/**
 + * This class simulates a full authentication process using pac4j.
 + */
 +public class Pac4jProviderTest {
 +
 +    private static final String LOCALHOST = "127.0.0.1";
 +    private static final String HADOOP_SERVICE_URL = "https://"; + LOCALHOST + 
":8443/gateway/sandox/webhdfs/v1/tmp?op=LISTSTATUS";
 +    private static final String KNOXSSO_SERVICE_URL = "https://"; + LOCALHOST 
+ ":8443/gateway/idp/api/v1/websso";
 +    private static final String PAC4J_CALLBACK_URL = KNOXSSO_SERVICE_URL;
 +    private static final String ORIGINAL_URL = "originalUrl";
 +    private static final String CLUSTER_NAME = "knox";
 +    private static final String PAC4J_PASSWORD = "pwdfortest";
 +    private static final String CLIENT_CLASS = 
IndirectBasicAuthClient.class.getSimpleName();
 +    private static final String USERNAME = "jleleu";
 +
 +    @Test
 +    public void test() throws Exception {
 +        final AliasService aliasService = mock(AliasService.class);
 +        when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, 
KnoxSessionStore.PAC4J_PASSWORD, 
true)).thenReturn(PAC4J_PASSWORD.toCharArray());
 +        when(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, 
KnoxSessionStore.PAC4J_PASSWORD)).thenReturn(PAC4J_PASSWORD.toCharArray());
 +
 +        final DefaultCryptoService cryptoService = new DefaultCryptoService();
 +        cryptoService.setAliasService(aliasService);
 +
 +        final GatewayServices services = mock(GatewayServices.class);
 +        
when(services.getService(GatewayServices.CRYPTO_SERVICE)).thenReturn(cryptoService);
 +        
when(services.getService(GatewayServices.ALIAS_SERVICE)).thenReturn(aliasService);
 +
 +        final ServletContext context = mock(ServletContext.class);
 +        
when(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).thenReturn(services);
 +        
when(context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).thenReturn(CLUSTER_NAME);
 +
 +        final FilterConfig config = mock(FilterConfig.class);
 +        when(config.getServletContext()).thenReturn(context);
 +        
when(config.getInitParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_URL)).thenReturn(PAC4J_CALLBACK_URL);
-         
when(config.getInitParameter(Pac4jConstants.CLIENT_NAME)).thenReturn(Pac4jDispatcherFilter.TEST_BASIC_AUTH);
++        
when(config.getInitParameter("clientName")).thenReturn(Pac4jDispatcherFilter.TEST_BASIC_AUTH);
 +
 +        final Pac4jDispatcherFilter dispatcher = new Pac4jDispatcherFilter();
 +        dispatcher.init(config);
 +        final Pac4jIdentityAdapter adapter = new Pac4jIdentityAdapter();
 +        adapter.init(config);
-         adapter.setAuditor(mock(Auditor.class));
++        Pac4jIdentityAdapter.setAuditor(mock(Auditor.class));
 +        final AuditService auditService = mock(AuditService.class);
 +        when(auditService.getContext()).thenReturn(mock(AuditContext.class));
-         adapter.setAuditService(auditService);
++        Pac4jIdentityAdapter.setAuditService(auditService);
 +
 +        // step 1: call the KnoxSSO service with an original url pointing to 
an Hadoop service (redirected by the SSOCookieProvider)
 +        MockHttpServletRequest request = new MockHttpServletRequest();
 +        request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" 
+ HADOOP_SERVICE_URL);
 +        request.setCookies(new Cookie[0]);
 +        request.setServerName(LOCALHOST);
 +        MockHttpServletResponse response = new MockHttpServletResponse();
 +        FilterChain filterChain = mock(FilterChain.class);
 +        dispatcher.doFilter(request, response, filterChain);
 +        // it should be a redirection to the idp topology
 +        assertEquals(302, response.getStatus());
 +        assertEquals(PAC4J_CALLBACK_URL + "?" + 
Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER + "=true&" + 
Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS, 
response.getHeaders().get("Location"));
 +        // we should have one cookie for the saved requested url
 +        List<Cookie> cookies = response.getCookies();
 +        assertEquals(1, cookies.size());
 +        final Cookie requestedUrlCookie = cookies.get(0);
 +        assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.REQUESTED_URL, requestedUrlCookie.getName());
 +
 +        // step 2: send credentials to the callback url (callback from the 
identity provider)
 +        request = new MockHttpServletRequest();
 +        request.setCookies(new Cookie[]{requestedUrlCookie});
 +        request.setRequestURL(PAC4J_CALLBACK_URL + "?" + 
Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER + "=true&" + 
Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + 
Clients.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS);
 +        request.addParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER, 
"true");
 +        request.addParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER, 
CLIENT_CLASS);
 +        request.addHeader("Authorization", "Basic amxlbGV1OmpsZWxldQ==");
 +        request.setServerName(LOCALHOST);
 +        response = new MockHttpServletResponse();
 +        filterChain = mock(FilterChain.class);
 +        dispatcher.doFilter(request, response, filterChain);
 +        // it should be a redirection to the original url
 +        assertEquals(302, response.getStatus());
 +        assertEquals(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + 
HADOOP_SERVICE_URL, response.getHeaders().get("Location"));
 +        // we should have 3 cookies among with the user profile
 +        cookies = response.getCookies();
 +        Map<String, String> mapCookies = new HashMap<>();
 +        assertEquals(3, cookies.size());
 +        for (final Cookie cookie : cookies) {
 +            mapCookies.put(cookie.getName(), cookie.getValue());
 +        }
 +        assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
CLIENT_CLASS + "$attemptedAuthentication"));
-         assertNotNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.USER_PROFILE));
++        assertNotNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.USER_PROFILES));
 +        assertNull(mapCookies.get(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.REQUESTED_URL));
 +
 +        // step 3: turn pac4j identity into KnoxSSO identity
 +        request = new MockHttpServletRequest();
 +        request.setCookies(cookies.toArray(new Cookie[cookies.size()]));
 +        request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" 
+ HADOOP_SERVICE_URL);
 +        request.setServerName(LOCALHOST);
 +        response = new MockHttpServletResponse();
 +        filterChain = mock(FilterChain.class);
 +        dispatcher.doFilter(request, response, filterChain);
 +        assertEquals(0, response.getStatus());
 +        adapter.doFilter(request, response, filterChain);
 +        cookies = response.getCookies();
 +        assertEquals(1, cookies.size());
 +        final Cookie userProfileCookie = cookies.get(0);
 +        // the user profile has been cleaned
-         assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.USER_PROFILE, userProfileCookie.getName());
++        assertEquals(KnoxSessionStore.PAC4J_SESSION_PREFIX + 
Pac4jConstants.USER_PROFILES, userProfileCookie.getName());
 +        assertNull(userProfileCookie.getValue());
 +        assertEquals(USERNAME, adapter.getTestIdentifier());
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
----------------------------------------------------------------------
diff --cc 
gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
index 7f52b51,0000000..5fc3148
mode 100644,000000..100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
@@@ -1,226 -1,0 +1,240 @@@
 +/**
 + * 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.knox.gateway.services.token.impl;
 +
 +import java.security.KeyStoreException;
 +import java.security.Principal;
 +import java.security.PublicKey;
 +import java.security.interfaces.RSAPrivateKey;
 +import java.security.interfaces.RSAPublicKey;
 +import java.util.Map;
++import java.util.Set;
 +import java.util.List;
 +import java.util.ArrayList;
++import java.util.HashSet;
 +
 +import javax.security.auth.Subject;
 +
 +import org.apache.knox.gateway.config.GatewayConfig;
 +import org.apache.knox.gateway.services.Service;
 +import org.apache.knox.gateway.services.ServiceLifecycleException;
 +import org.apache.knox.gateway.services.security.AliasService;
 +import org.apache.knox.gateway.services.security.AliasServiceException;
 +import org.apache.knox.gateway.services.security.KeystoreService;
 +import org.apache.knox.gateway.services.security.KeystoreServiceException;
 +import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
 +import org.apache.knox.gateway.services.security.token.TokenServiceException;
 +import org.apache.knox.gateway.services.security.token.impl.JWT;
 +import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 +
 +import com.nimbusds.jose.JWSSigner;
 +import com.nimbusds.jose.JWSVerifier;
 +import com.nimbusds.jose.crypto.RSASSASigner;
 +import com.nimbusds.jose.crypto.RSASSAVerifier;
 +
 +public class DefaultTokenAuthorityService implements JWTokenAuthority, 
Service {
 +
 +  private static final String SIGNING_KEY_PASSPHRASE = 
"signing.key.passphrase";
++  private static final Set<String> SUPPORTED_SIG_ALGS = new HashSet<>();
 +  private AliasService as = null;
 +  private KeystoreService ks = null;
 +  String signingKeyAlias = null;
 +
++  static {
++      // Only standard RSA signature algorithms are accepted
++      // https://tools.ietf.org/html/rfc7518
++      SUPPORTED_SIG_ALGS.add("RS256");
++      SUPPORTED_SIG_ALGS.add("RS384");
++      SUPPORTED_SIG_ALGS.add("RS512");
++      SUPPORTED_SIG_ALGS.add("PS256");
++      SUPPORTED_SIG_ALGS.add("PS384");
++      SUPPORTED_SIG_ALGS.add("PS512");
++  }
++
 +  public void setKeystoreService(KeystoreService ks) {
 +    this.ks = ks;
 +  }
 +
 +  public void setAliasService(AliasService as) {
 +    this.as = as;
 +  }
 +
 +  /* (non-Javadoc)
 +   * @see 
org.apache.knox.gateway.provider.federation.jwt.JWTokenAuthority#issueToken(javax.security.auth.Subject,
 java.lang.String)
 +   */
 +  @Override
 +  public JWT issueToken(Subject subject, String algorithm) throws 
TokenServiceException {
 +    Principal p = (Principal) subject.getPrincipals().toArray()[0];
 +    return issueToken(p, algorithm);
 +  }
 +
 +  /* (non-Javadoc)
 +   * @see 
org.apache.knox.gateway.provider.federation.jwt.JWTokenAuthority#issueToken(java.security.Principal,
 java.lang.String)
 +   */
 +  @Override
 +  public JWT issueToken(Principal p, String algorithm) throws 
TokenServiceException {
 +    return issueToken(p, null, algorithm);
 +  }
 +
 +  /* (non-Javadoc)
 +   * @see 
org.apache.knox.gateway.provider.federation.jwt.JWTokenAuthority#issueToken(java.security.Principal,
 java.lang.String, long expires)
 +   */
 +  @Override
 +  public JWT issueToken(Principal p, String algorithm, long expires) throws 
TokenServiceException {
 +    return issueToken(p, (String)null, algorithm, expires);
 +  }
 +
 +  public JWT issueToken(Principal p, String audience, String algorithm)
 +      throws TokenServiceException {
 +    return issueToken(p, audience, algorithm, -1);
 +  }
 +
 +  /* (non-Javadoc)
 +   * @see 
org.apache.knox.gateway.provider.federation.jwt.JWTokenAuthority#issueToken(java.security.Principal,
 java.lang.String, java.lang.String)
 +   */
 +  @Override
 +  public JWT issueToken(Principal p, String audience, String algorithm, long 
expires)
 +      throws TokenServiceException {
-     ArrayList<String> audiences = null;
++    List<String> audiences = null;
 +    if (audience != null) {
 +      audiences = new ArrayList<String>();
 +      audiences.add(audience);
 +    }
 +    return issueToken(p, audiences, algorithm, expires);
 +  }
 +
 +  @Override
 +  public JWT issueToken(Principal p, List<String> audiences, String 
algorithm, long expires)
 +      throws TokenServiceException {
 +    String[] claimArray = new String[4];
 +    claimArray[0] = "KNOXSSO";
 +    claimArray[1] = p.getName();
 +    claimArray[2] = null;
 +    if (expires == -1) {
 +      claimArray[3] = null;
 +    }
 +    else {
 +      claimArray[3] = String.valueOf(expires);
 +    }
 +
-     JWTToken token = null;
-     if ("RS256".equals(algorithm)) {
-       token = new JWTToken("RS256", claimArray, audiences);
++    JWT token = null;
++    if (SUPPORTED_SIG_ALGS.contains(algorithm)) {
++      token = new JWTToken(algorithm, claimArray, audiences);
 +      RSAPrivateKey key;
 +      char[] passphrase = null;
 +      try {
 +        passphrase = getSigningKeyPassphrase();
 +      } catch (AliasServiceException e) {
 +        throw new TokenServiceException(e);
 +      }
 +      try {
 +        key = (RSAPrivateKey) ks.getSigningKey(getSigningKeyAlias(),
 +            passphrase);
 +        JWSSigner signer = new RSASSASigner(key);
 +        token.sign(signer);
 +      } catch (KeystoreServiceException e) {
 +        throw new TokenServiceException(e);
 +      }
 +    }
 +    else {
 +      throw new TokenServiceException("Cannot issue token - Unsupported 
algorithm");
 +    }
 +
 +    return token;
 +  }
 +
 +  private char[] getSigningKeyPassphrase() throws AliasServiceException {
 +    char[] phrase = as.getPasswordFromAliasForGateway(SIGNING_KEY_PASSPHRASE);
 +    if (phrase == null) {
 +      phrase = as.getGatewayIdentityPassphrase();
 +    }
 +    return phrase;
 +  }
 +
 +  private String getSigningKeyAlias() {
 +    if (signingKeyAlias == null) {
 +      return "gateway-identity";
 +    }
 +    return signingKeyAlias;
 +  }
 +
 +  @Override
 +  public boolean verifyToken(JWT token)
 +      throws TokenServiceException {
 +    return verifyToken(token, null);
 +  }
 +
 +  @Override
 +  public boolean verifyToken(JWT token, RSAPublicKey publicKey)
 +      throws TokenServiceException {
 +    boolean rc = false;
 +    PublicKey key;
 +    try {
 +      if (publicKey == null) {
 +        key = 
ks.getSigningKeystore().getCertificate(getSigningKeyAlias()).getPublicKey();
 +      }
 +      else {
 +        key = publicKey;
 +      }
 +      JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) key);
 +      // TODO: interrogate the token for issuer claim in order to determine 
the public key to use for verification
 +      // consider jwk for specifying the key too
 +      rc = token.verify(verifier);
 +    } catch (KeyStoreException e) {
 +      throw new TokenServiceException("Cannot verify token.", e);
 +    } catch (KeystoreServiceException e) {
 +      throw new TokenServiceException("Cannot verify token.", e);
 +    }
 +    return rc;
 +  }
 +
 +  @Override
 +  public void init(GatewayConfig config, Map<String, String> options)
 +      throws ServiceLifecycleException {
 +    if (as == null || ks == null) {
 +      throw new ServiceLifecycleException("Alias or Keystore service is not 
set");
 +    }
 +    signingKeyAlias = config.getSigningKeyAlias();
 +
 +    @SuppressWarnings("unused")
 +    RSAPrivateKey key;
 +    char[] passphrase = null;
 +    try {
 +      passphrase = as.getPasswordFromAliasForGateway(SIGNING_KEY_PASSPHRASE);
 +      if (passphrase != null) {
 +        key = (RSAPrivateKey) ks.getSigningKey(getSigningKeyAlias(),
 +            passphrase);
 +        if (key == null) {
 +          throw new ServiceLifecycleException("Provisioned passphrase cannot 
be used to acquire signing key.");
 +        }
 +      }
 +    } catch (AliasServiceException e) {
 +      throw new ServiceLifecycleException("Provisioned signing key passphrase 
cannot be acquired.", e);
 +    } catch (KeystoreServiceException e) {
 +      throw new ServiceLifecycleException("Provisioned signing key passphrase 
cannot be acquired.", e);
 +    }
 +  }
 +
 +  @Override
 +  public void start() throws ServiceLifecycleException {
 +  }
 +
 +  @Override
 +  public void stop() throws ServiceLifecycleException {
 +  }
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
----------------------------------------------------------------------
diff --cc 
gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
index 9f6f762,0000000..455b0fa
mode 100644,000000..100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
@@@ -1,673 -1,0 +1,689 @@@
 +/**
 + * 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.knox.gateway.services.topology.impl;
 +
 +
 +import org.apache.commons.digester3.Digester;
 +import org.apache.commons.digester3.binder.DigesterLoader;
 +import org.apache.commons.io.FileUtils;
 +import org.apache.commons.io.FilenameUtils;
 +import org.apache.commons.io.monitor.FileAlterationListener;
 +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
 +import org.apache.commons.io.monitor.FileAlterationMonitor;
 +import org.apache.commons.io.monitor.FileAlterationObserver;
 +import org.apache.knox.gateway.GatewayMessages;
 +import org.apache.knox.gateway.audit.api.Action;
 +import org.apache.knox.gateway.audit.api.ActionOutcome;
 +import org.apache.knox.gateway.audit.api.AuditServiceFactory;
 +import org.apache.knox.gateway.audit.api.Auditor;
 +import org.apache.knox.gateway.audit.api.ResourceType;
 +import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
 +import org.apache.knox.gateway.config.GatewayConfig;
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.knox.gateway.service.definition.ServiceDefinition;
 +import org.apache.knox.gateway.services.ServiceLifecycleException;
 +import org.apache.knox.gateway.services.topology.TopologyService;
 +import org.apache.knox.gateway.topology.Topology;
 +import org.apache.knox.gateway.topology.TopologyEvent;
 +import org.apache.knox.gateway.topology.TopologyListener;
 +import org.apache.knox.gateway.topology.TopologyMonitor;
 +import org.apache.knox.gateway.topology.TopologyProvider;
 +import org.apache.knox.gateway.topology.builder.TopologyBuilder;
 +import org.apache.knox.gateway.topology.validation.TopologyValidator;
 +import org.apache.knox.gateway.topology.xml.AmbariFormatXmlTopologyRules;
 +import org.apache.knox.gateway.topology.xml.KnoxFormatXmlTopologyRules;
 +import org.apache.knox.gateway.util.ServiceDefinitionsLoader;
 +import org.apache.knox.gateway.services.security.AliasService;
 +import org.apache.knox.gateway.topology.simple.SimpleDescriptorHandler;
 +import org.eclipse.persistence.jaxb.JAXBContextProperties;
 +import org.xml.sax.SAXException;
 +
 +import javax.xml.bind.JAXBContext;
 +import javax.xml.bind.JAXBException;
 +import javax.xml.bind.Marshaller;
 +import java.io.File;
 +import java.io.FileFilter;
 +import java.io.IOException;
 +import java.net.URISyntaxException;
 +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.Map;
 +import java.util.Set;
 +
 +import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
 +
 +
 +public class DefaultTopologyService
 +    extends FileAlterationListenerAdaptor
 +    implements TopologyService, TopologyMonitor, TopologyProvider, 
FileFilter, FileAlterationListener {
 +
 +  private static Auditor auditor = 
AuditServiceFactory.getAuditService().getAuditor(
 +    AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
 +    AuditConstants.KNOX_COMPONENT_NAME);
 +
 +  private static final List<String> SUPPORTED_TOPOLOGY_FILE_EXTENSIONS = new 
ArrayList<String>();
 +  static {
 +    SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("xml");
 +    SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("conf");
 +  }
 +
 +  private static GatewayMessages log = 
MessagesFactory.get(GatewayMessages.class);
 +  private static DigesterLoader digesterLoader = newLoader(new 
KnoxFormatXmlTopologyRules(), new AmbariFormatXmlTopologyRules());
 +  private List<FileAlterationMonitor> monitors = new ArrayList<>();
 +  private File topologiesDirectory;
 +  private File descriptorsDirectory;
 +
 +  private Set<TopologyListener> listeners;
 +  private volatile Map<File, Topology> topologies;
 +  private AliasService aliasService;
 +
 +
 +  private Topology loadTopology(File file) throws IOException, SAXException, 
URISyntaxException, InterruptedException {
 +    final long TIMEOUT = 250; //ms
 +    final long DELAY = 50; //ms
 +    log.loadingTopologyFile(file.getAbsolutePath());
 +    Topology topology;
 +    long start = System.currentTimeMillis();
 +    while (true) {
 +      try {
 +        topology = loadTopologyAttempt(file);
 +        break;
 +      } catch (IOException e) {
 +        if (System.currentTimeMillis() - start < TIMEOUT) {
 +          log.failedToLoadTopologyRetrying(file.getAbsolutePath(), 
Long.toString(DELAY), e);
 +          Thread.sleep(DELAY);
 +        } else {
 +          throw e;
 +        }
 +      } catch (SAXException e) {
 +        if (System.currentTimeMillis() - start < TIMEOUT) {
 +          log.failedToLoadTopologyRetrying(file.getAbsolutePath(), 
Long.toString(DELAY), e);
 +          Thread.sleep(DELAY);
 +        } else {
 +          throw e;
 +        }
 +      }
 +    }
 +    return topology;
 +  }
 +
 +  private Topology loadTopologyAttempt(File file) throws IOException, 
SAXException, URISyntaxException {
 +    Topology topology;
 +    Digester digester = digesterLoader.newDigester();
 +    TopologyBuilder topologyBuilder = 
digester.parse(FileUtils.openInputStream(file));
 +    if (null == topologyBuilder) {
 +      return null;
 +    }
 +    topology = topologyBuilder.build();
 +    topology.setUri(file.toURI());
 +    topology.setName(FilenameUtils.removeExtension(file.getName()));
 +    topology.setTimestamp(file.lastModified());
 +    return topology;
 +  }
 +
 +  private void redeployTopology(Topology topology) {
 +    File topologyFile = new File(topology.getUri());
 +    try {
 +      TopologyValidator tv = new TopologyValidator(topology);
 +
 +      if(tv.validateTopology()) {
 +        throw new SAXException(tv.getErrorString());
 +      }
 +
 +      long start = System.currentTimeMillis();
 +      long limit = 1000L; // One second.
 +      long elapsed = 1;
 +      while (elapsed <= limit) {
 +        try {
 +          long origTimestamp = topologyFile.lastModified();
 +          long setTimestamp = Math.max(System.currentTimeMillis(), 
topologyFile.lastModified() + elapsed);
 +          if(topologyFile.setLastModified(setTimestamp)) {
 +            long newTimstamp = topologyFile.lastModified();
 +            if(newTimstamp > origTimestamp) {
 +              break;
 +            } else {
 +              Thread.sleep(10);
 +              elapsed = System.currentTimeMillis() - start;
 +              continue;
 +            }
 +          } else {
 +            auditor.audit(Action.REDEPLOY, topology.getName(), 
ResourceType.TOPOLOGY,
 +                ActionOutcome.FAILURE);
 +            log.failedToRedeployTopology(topology.getName());
 +            break;
 +          }
 +        } catch (InterruptedException e) {
 +          auditor.audit(Action.REDEPLOY, topology.getName(), 
ResourceType.TOPOLOGY,
 +              ActionOutcome.FAILURE);
 +          log.failedToRedeployTopology(topology.getName(), e);
 +          e.printStackTrace();
 +        }
 +      }
 +    } catch (SAXException e) {
 +      auditor.audit(Action.REDEPLOY, topology.getName(), 
ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
 +      log.failedToRedeployTopology(topology.getName(), e);
 +    }
 +  }
 +
 +  private List<TopologyEvent> createChangeEvents(
 +      Map<File, Topology> oldTopologies,
 +      Map<File, Topology> newTopologies) {
 +    ArrayList<TopologyEvent> events = new ArrayList<TopologyEvent>();
 +    // Go through the old topologies and find anything that was deleted.
 +    for (File file : oldTopologies.keySet()) {
 +      if (!newTopologies.containsKey(file)) {
 +        events.add(new TopologyEvent(TopologyEvent.Type.DELETED, 
oldTopologies.get(file)));
 +      }
 +    }
 +    // Go through the new topologies and figure out what was updated vs added.
 +    for (File file : newTopologies.keySet()) {
 +      if (oldTopologies.containsKey(file)) {
 +        Topology oldTopology = oldTopologies.get(file);
 +        Topology newTopology = newTopologies.get(file);
 +        if (newTopology.getTimestamp() > oldTopology.getTimestamp()) {
 +          events.add(new TopologyEvent(TopologyEvent.Type.UPDATED, 
newTopologies.get(file)));
 +        }
 +      } else {
 +        events.add(new TopologyEvent(TopologyEvent.Type.CREATED, 
newTopologies.get(file)));
 +      }
 +    }
 +    return events;
 +  }
 +
 +  private File calculateAbsoluteTopologiesDir(GatewayConfig config) {
 +    String normalizedTopologyDir = 
FilenameUtils.normalize(config.getGatewayTopologyDir());
 +    File topoDir = new File(normalizedTopologyDir);
 +    topoDir = topoDir.getAbsoluteFile();
 +    return topoDir;
 +  }
 +
 +  private File calculateAbsoluteConfigDir(GatewayConfig config) {
 +    File configDir = null;
 +
 +    String path = FilenameUtils.normalize(config.getGatewayConfDir());
 +    if (path != null) {
 +      configDir = new File(config.getGatewayConfDir());
 +    } else {
 +      configDir = (new File(config.getGatewayTopologyDir())).getParentFile();
 +    }
 +    configDir = configDir.getAbsoluteFile();
 +
 +    return configDir;
 +  }
 +
 +  private void  initListener(FileAlterationMonitor  monitor,
 +                            File                   directory,
 +                            FileFilter             filter,
 +                            FileAlterationListener listener) {
 +    monitors.add(monitor);
 +    FileAlterationObserver observer = new FileAlterationObserver(directory, 
filter);
 +    observer.addListener(listener);
 +    monitor.addObserver(observer);
 +  }
 +
 +  private void initListener(File directory, FileFilter filter, 
FileAlterationListener listener) throws IOException, SAXException {
 +    // Increasing the monitoring interval to 5 seconds as profiling has shown
 +    // this is rather expensive in terms of generated garbage objects.
 +    initListener(new FileAlterationMonitor(5000L), directory, filter, 
listener);
 +  }
 +
 +  private Map<File, Topology> loadTopologies(File directory) {
 +    Map<File, Topology> map = new HashMap<>();
 +    if (directory.isDirectory() && directory.canRead()) {
 +      for (File file : directory.listFiles(this)) {
 +        try {
 +          Topology loadTopology = loadTopology(file);
 +          if (null != loadTopology) {
 +            map.put(file, loadTopology);
 +          } else {
 +            auditor.audit(Action.LOAD, file.getAbsolutePath(), 
ResourceType.TOPOLOGY,
 +              ActionOutcome.FAILURE);
 +            log.failedToLoadTopology(file.getAbsolutePath());
 +          }
 +        } catch (IOException e) {
 +          // Maybe it makes sense to throw exception
 +          auditor.audit(Action.LOAD, file.getAbsolutePath(), 
ResourceType.TOPOLOGY,
 +            ActionOutcome.FAILURE);
 +          log.failedToLoadTopology(file.getAbsolutePath(), e);
 +        } catch (SAXException e) {
 +          // Maybe it makes sense to throw exception
 +          auditor.audit(Action.LOAD, file.getAbsolutePath(), 
ResourceType.TOPOLOGY,
 +            ActionOutcome.FAILURE);
 +          log.failedToLoadTopology(file.getAbsolutePath(), e);
 +        } catch (Exception e) {
 +          // Maybe it makes sense to throw exception
 +          auditor.audit(Action.LOAD, file.getAbsolutePath(), 
ResourceType.TOPOLOGY,
 +            ActionOutcome.FAILURE);
 +          log.failedToLoadTopology(file.getAbsolutePath(), e);
 +        }
 +      }
 +    }
 +    return map;
 +  }
 +
 +  public void setAliasService(AliasService as) {
 +    this.aliasService = as;
 +  }
 +
 +  public void deployTopology(Topology t){
 +
 +    try {
 +      File temp = new File(topologiesDirectory.getAbsolutePath() + "/" + 
t.getName() + ".xml.temp");
 +      Package topologyPkg = Topology.class.getPackage();
 +      String pkgName = topologyPkg.getName();
 +      String bindingFile = pkgName.replace(".", "/") + 
"/topology_binding-xml.xml";
 +
 +      Map<String, Object> properties = new HashMap<>(1);
 +      properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindingFile);
 +      JAXBContext jc = JAXBContext.newInstance(pkgName, 
Topology.class.getClassLoader(), properties);
 +      Marshaller mr = jc.createMarshaller();
 +
 +      mr.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 +      mr.marshal(t, temp);
 +
 +      File topology = new File(topologiesDirectory.getAbsolutePath() + "/" + 
t.getName() + ".xml");
 +      if(!temp.renameTo(topology)) {
 +        FileUtils.forceDelete(temp);
 +        throw new IOException("Could not rename temp file");
 +      }
 +
 +      // This code will check if the topology is valid, and retrieve the 
errors if it is not.
 +      TopologyValidator validator = new TopologyValidator( 
topology.getAbsolutePath() );
 +      if( !validator.validateTopology() ){
 +        throw new SAXException( validator.getErrorString() );
 +      }
 +
 +
 +    } catch (JAXBException e) {
 +      auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, 
ActionOutcome.FAILURE);
 +      log.failedToDeployTopology(t.getName(), e);
 +    } catch (IOException io) {
 +      auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, 
ActionOutcome.FAILURE);
 +      log.failedToDeployTopology(t.getName(), io);
 +    } catch (SAXException sx){
 +      auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, 
ActionOutcome.FAILURE);
 +      log.failedToDeployTopology(t.getName(), sx);
 +    }
 +    reloadTopologies();
 +  }
 +
 +  public void redeployTopologies(String topologyName) {
 +
 +    for (Topology topology : getTopologies()) {
 +      if (topologyName == null || topologyName.equals(topology.getName())) {
 +        redeployTopology(topology);
 +      }
 +    }
 +
 +  }
 +
 +  public void reloadTopologies() {
 +    try {
 +      synchronized (this) {
 +        Map<File, Topology> oldTopologies = topologies;
 +        Map<File, Topology> newTopologies = 
loadTopologies(topologiesDirectory);
 +        List<TopologyEvent> events = createChangeEvents(oldTopologies, 
newTopologies);
 +        topologies = newTopologies;
 +        notifyChangeListeners(events);
 +      }
 +    } catch (Exception e) {
 +      // Maybe it makes sense to throw exception
 +      log.failedToReloadTopologies(e);
 +    }
 +  }
 +
 +  public void deleteTopology(Topology t) {
 +    File topoDir = topologiesDirectory;
 +
 +    if(topoDir.isDirectory() && topoDir.canRead()) {
 +      File[] results = topoDir.listFiles();
 +      for (File f : results) {
 +        String fName = FilenameUtils.getBaseName(f.getName());
 +        if(fName.equals(t.getName())) {
 +          f.delete();
 +        }
 +      }
 +    }
 +    reloadTopologies();
 +  }
 +
 +  private void notifyChangeListeners(List<TopologyEvent> events) {
 +    for (TopologyListener listener : listeners) {
 +      try {
 +        listener.handleTopologyEvent(events);
 +      } catch (RuntimeException e) {
 +        auditor.audit(Action.LOAD, "Topology_Event", ResourceType.TOPOLOGY, 
ActionOutcome.FAILURE);
 +        log.failedToHandleTopologyEvents(e);
 +      }
 +    }
 +  }
 +
 +  public Map<String, List<String>> getServiceTestURLs(Topology t, 
GatewayConfig config) {
 +    File tFile = null;
 +    Map<String, List<String>> urls = new HashMap<>();
 +    if(topologiesDirectory.isDirectory() && topologiesDirectory.canRead()) {
 +      for(File f : topologiesDirectory.listFiles()){
 +        if(FilenameUtils.removeExtension(f.getName()).equals(t.getName())){
 +          tFile = f;
 +        }
 +      }
 +    }
 +    Set<ServiceDefinition> defs;
 +    if(tFile != null) {
 +      defs = ServiceDefinitionsLoader.getServiceDefinitions(new 
File(config.getGatewayServicesDir()));
 +
 +      for(ServiceDefinition def : defs) {
 +        urls.put(def.getRole(), def.getTestURLs());
 +      }
 +    }
 +    return urls;
 +  }
 +
 +  public Collection<Topology> getTopologies() {
 +    Map<File, Topology> map = topologies;
 +    return Collections.unmodifiableCollection(map.values());
 +  }
 +
 +  @Override
 +  public void addTopologyChangeListener(TopologyListener listener) {
 +    listeners.add(listener);
 +  }
 +
 +  @Override
 +  public void startMonitor() throws Exception {
 +    for (FileAlterationMonitor monitor : monitors) {
 +      monitor.start();
 +    }
 +  }
 +
 +  @Override
 +  public void stopMonitor() throws Exception {
 +    for (FileAlterationMonitor monitor : monitors) {
 +      monitor.stop();
 +    }
 +  }
 +
 +  @Override
 +  public boolean accept(File file) {
 +    boolean accept = false;
 +    if (!file.isDirectory() && file.canRead()) {
 +      String extension = FilenameUtils.getExtension(file.getName());
 +      if (SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.contains(extension)) {
 +        accept = true;
 +      }
 +    }
 +    return accept;
 +  }
 +
 +  @Override
 +  public void onFileCreate(File file) {
 +    onFileChange(file);
 +  }
 +
 +  @Override
 +  public void onFileDelete(java.io.File file) {
 +    // For full topology descriptors, we need to make sure to delete any 
corresponding simple descriptors to prevent
 +    // unintended subsequent generation of the topology descriptor
 +    for (String ext : DescriptorsMonitor.SUPPORTED_EXTENSIONS) {
 +      File simpleDesc =
 +              new File(descriptorsDirectory, 
FilenameUtils.getBaseName(file.getName()) + "." + ext);
 +      if (simpleDesc.exists()) {
 +        simpleDesc.delete();
 +      }
 +    }
 +
 +    onFileChange(file);
 +  }
 +
 +  @Override
 +  public void onFileChange(File file) {
 +    reloadTopologies();
 +  }
 +
 +  @Override
 +  public void stop() {
 +
 +  }
 +
 +  @Override
 +  public void start() {
 +
 +  }
 +
 +  @Override
 +  public void init(GatewayConfig config, Map<String, String> options) throws 
ServiceLifecycleException {
 +
 +    try {
 +      listeners = new HashSet<>();
 +      topologies = new HashMap<>();
 +
 +      topologiesDirectory = calculateAbsoluteTopologiesDir(config);
 +
 +      File configDirectory = calculateAbsoluteConfigDir(config);
 +      descriptorsDirectory = new File(configDirectory, "descriptors");
 +      File sharedProvidersDirectory = new File(configDirectory, 
"shared-providers");
 +
 +      // Add support for conf/topologies
 +      initListener(topologiesDirectory, this, this);
 +
 +      // Add support for conf/descriptors
 +      DescriptorsMonitor dm = new DescriptorsMonitor(topologiesDirectory, 
aliasService);
 +      initListener(descriptorsDirectory,
 +                   dm,
 +                   dm);
 +
 +      // Add support for conf/shared-providers
 +      SharedProviderConfigMonitor spm = new SharedProviderConfigMonitor(dm, 
descriptorsDirectory);
 +      initListener(sharedProvidersDirectory, spm, spm);
 +
++      // For all the descriptors currently in the descriptors dir at start-up 
time, trigger topology generation.
++      // This happens prior to the start-up loading of the topologies.
++      String[] descriptorFilenames =  descriptorsDirectory.list();
++      if (descriptorFilenames != null) {
++          for (String descriptorFilename : descriptorFilenames) {
++              if (DescriptorsMonitor.isDescriptorFile(descriptorFilename)) {
++                  dm.onFileChange(new File(descriptorsDirectory, 
descriptorFilename));
++              }
++          }
++      }
++
 +    } catch (IOException | SAXException io) {
 +      throw new ServiceLifecycleException(io.getMessage());
 +    }
 +  }
 +
 +
 +  /**
 +   * Change handler for simple descriptors
 +   */
 +  public static class DescriptorsMonitor extends FileAlterationListenerAdaptor
 +                                          implements FileFilter {
 +
 +    static final List<String> SUPPORTED_EXTENSIONS = new ArrayList<String>();
 +    static {
 +      SUPPORTED_EXTENSIONS.add("json");
 +      SUPPORTED_EXTENSIONS.add("yml");
++      SUPPORTED_EXTENSIONS.add("yaml");
 +    }
 +
 +    private File topologiesDir;
 +
 +    private AliasService aliasService;
 +
 +    private Map<String, List<String>> providerConfigReferences = new 
HashMap<>();
 +
 +
++    static boolean isDescriptorFile(String filename) {
++      return 
SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(filename));
++    }
++
 +    public DescriptorsMonitor(File topologiesDir, AliasService aliasService) {
 +      this.topologiesDir  = topologiesDir;
 +      this.aliasService   = aliasService;
 +    }
 +
 +    List<String> getReferencingDescriptors(String providerConfigPath) {
 +      List<String> result = providerConfigReferences.get(providerConfigPath);
 +      if (result == null) {
 +        result = Collections.emptyList();
 +      }
 +      return result;
 +    }
 +
 +    @Override
 +    public void onFileCreate(File file) {
 +      onFileChange(file);
 +    }
 +
 +    @Override
 +    public void onFileDelete(File file) {
 +      // For simple descriptors, we need to make sure to delete any 
corresponding full topology descriptors to trigger undeployment
 +      for (String ext : 
DefaultTopologyService.SUPPORTED_TOPOLOGY_FILE_EXTENSIONS) {
 +        File topologyFile =
 +                new File(topologiesDir, 
FilenameUtils.getBaseName(file.getName()) + "." + ext);
 +        if (topologyFile.exists()) {
 +          topologyFile.delete();
 +        }
 +      }
 +
 +      String normalizedFilePath = 
FilenameUtils.normalize(file.getAbsolutePath());
 +      String reference = null;
 +      for (Map.Entry<String, List<String>> entry : 
providerConfigReferences.entrySet()) {
 +        if (entry.getValue().contains(normalizedFilePath)) {
 +          reference = entry.getKey();
 +          break;
 +        }
 +      }
 +      if (reference != null) {
 +        providerConfigReferences.get(reference).remove(normalizedFilePath);
 +      }
 +    }
 +
 +    @Override
 +    public void onFileChange(File file) {
 +      try {
 +        // When a simple descriptor has been created or modified, generate 
the new topology descriptor
 +        Map<String, File> result = SimpleDescriptorHandler.handle(file, 
topologiesDir, aliasService);
 +
 +        // Add the provider config reference relationship for handling 
updates to the provider config
 +        String providerConfig = 
FilenameUtils.normalize(result.get("reference").getAbsolutePath());
 +        if (!providerConfigReferences.containsKey(providerConfig)) {
 +          providerConfigReferences.put(providerConfig, new 
ArrayList<String>());
 +        }
 +        List<String> refs = providerConfigReferences.get(providerConfig);
 +        String descriptorName = 
FilenameUtils.normalize(file.getAbsolutePath());
 +        if (!refs.contains(descriptorName)) {
 +          // Need to check if descriptor had previously referenced another 
provider config, so it can be removed
 +          for (List<String> descs : providerConfigReferences.values()) {
 +            if (descs.contains(descriptorName)) {
 +              descs.remove(descriptorName);
 +            }
 +          }
 +
 +          // Add the current reference relationship
 +          refs.add(descriptorName);
 +        }
 +      } catch (Exception e) {
 +        log.simpleDescriptorHandlingError(file.getName(), e);
 +      }
 +    }
 +
 +    @Override
 +    public boolean accept(File file) {
 +      boolean accept = false;
 +      if (!file.isDirectory() && file.canRead()) {
 +        String extension = FilenameUtils.getExtension(file.getName());
 +        if (SUPPORTED_EXTENSIONS.contains(extension)) {
 +          accept = true;
 +        }
 +      }
 +      return accept;
 +    }
 +  }
 +
 +  /**
 +   * Change handler for shared provider configurations
 +   */
 +  public static class SharedProviderConfigMonitor extends 
FileAlterationListenerAdaptor
 +          implements FileFilter {
 +
 +    static final List<String> SUPPORTED_EXTENSIONS = new ArrayList<>();
 +    static {
 +      SUPPORTED_EXTENSIONS.add("xml");
 +    }
 +
 +    private DescriptorsMonitor descriptorsMonitor;
 +    private File descriptorsDir;
 +
 +
 +    SharedProviderConfigMonitor(DescriptorsMonitor descMonitor, File 
descriptorsDir) {
 +      this.descriptorsMonitor = descMonitor;
 +      this.descriptorsDir     = descriptorsDir;
 +    }
 +
 +    @Override
 +    public void onFileCreate(File file) {
 +      onFileChange(file);
 +    }
 +
 +    @Override
 +    public void onFileDelete(File file) {
 +      onFileChange(file);
 +    }
 +
 +    @Override
 +    public void onFileChange(File file) {
 +      // For shared provider configuration, we need to update any simple 
descriptors that reference it
 +      for (File descriptor : getReferencingDescriptors(file)) {
 +        descriptor.setLastModified(System.currentTimeMillis());
 +      }
 +    }
 +
 +    private List<File> getReferencingDescriptors(File sharedProviderConfig) {
 +      List<File> references = new ArrayList<>();
 +
 +      for (File descriptor : descriptorsDir.listFiles()) {
 +        if 
(DescriptorsMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(descriptor.getName())))
 {
 +          for (String reference : 
descriptorsMonitor.getReferencingDescriptors(FilenameUtils.normalize(sharedProviderConfig.getAbsolutePath())))
 {
 +            references.add(new File(reference));
 +          }
 +        }
 +      }
 +
 +      return references;
 +    }
 +
 +    @Override
 +    public boolean accept(File file) {
 +      boolean accept = false;
 +      if (!file.isDirectory() && file.canRead()) {
 +        String extension = FilenameUtils.getExtension(file.getName());
 +        if (SUPPORTED_EXTENSIONS.contains(extension)) {
 +          accept = true;
 +        }
 +      }
 +      return accept;
 +    }
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
----------------------------------------------------------------------
diff --cc 
gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
index 85c0535,0000000..25997b1
mode 100644,000000..100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
@@@ -1,46 -1,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.knox.gateway.topology.simple;
 +
 +import java.util.List;
++import java.util.Map;
 +
 +public interface SimpleDescriptor {
 +
 +    String getName();
 +
 +    String getDiscoveryType();
 +
 +    String getDiscoveryAddress();
 +
 +    String getDiscoveryUser();
 +
 +    String getDiscoveryPasswordAlias();
 +
 +    String getClusterName();
 +
 +    String getProviderConfig();
 +
 +    List<Service> getServices();
 +
 +
 +    interface Service {
 +        String getName();
 +
++        Map<String, String> getParams();
++
 +        List<String> getURLs();
 +    }
- 
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
----------------------------------------------------------------------
diff --cc 
gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
index 16d5b81,0000000..b54432d
mode 100644,000000..100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
@@@ -1,234 -1,0 +1,267 @@@
 +/**
 + * 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.knox.gateway.topology.simple;
 +
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.knox.gateway.services.Service;
 +import 
org.apache.knox.gateway.topology.discovery.DefaultServiceDiscoveryConfig;
 +import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
 +import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryFactory;
 +import java.io.BufferedWriter;
 +import java.io.File;
 +import java.io.FileInputStream;
 +import java.io.FileWriter;
 +import java.io.InputStreamReader;
 +import java.io.IOException;
 +
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +
 +
 +/**
 + * Processes simple topology descriptors, producing full topology files, 
which can subsequently be deployed to the
 + * gateway.
 + */
 +public class SimpleDescriptorHandler {
 +
 +    private static final Service[] NO_GATEWAY_SERVICES = new Service[]{};
 +
 +    private static final SimpleDescriptorMessages log = 
MessagesFactory.get(SimpleDescriptorMessages.class);
 +
 +    public static Map<String, File> handle(File desc) throws IOException {
 +        return handle(desc, NO_GATEWAY_SERVICES);
 +    }
 +
 +    public static Map<String, File> handle(File desc, 
Service...gatewayServices) throws IOException {
 +        return handle(desc, desc.getParentFile(), gatewayServices);
 +    }
 +
 +    public static Map<String, File> handle(File desc, File destDirectory) 
throws IOException {
 +        return handle(desc, destDirectory, NO_GATEWAY_SERVICES);
 +    }
 +
 +    public static Map<String, File> handle(File desc, File destDirectory, 
Service...gatewayServices) throws IOException {
 +        return handle(SimpleDescriptorFactory.parse(desc.getAbsolutePath()), 
desc.getParentFile(), destDirectory, gatewayServices);
 +    }
 +
 +    public static Map<String, File> handle(SimpleDescriptor desc, File 
srcDirectory, File destDirectory) {
 +        return handle(desc, srcDirectory, destDirectory, NO_GATEWAY_SERVICES);
 +    }
 +
 +    public static Map<String, File> handle(SimpleDescriptor desc, File 
srcDirectory, File destDirectory, Service...gatewayServices) {
 +        Map<String, File> result = new HashMap<>();
 +
 +        File topologyDescriptor;
 +
 +        DefaultServiceDiscoveryConfig sdc = new 
DefaultServiceDiscoveryConfig(desc.getDiscoveryAddress());
 +        sdc.setUser(desc.getDiscoveryUser());
 +        sdc.setPasswordAlias(desc.getDiscoveryPasswordAlias());
 +        ServiceDiscovery sd = 
ServiceDiscoveryFactory.get(desc.getDiscoveryType(), gatewayServices);
 +        ServiceDiscovery.Cluster cluster = sd.discover(sdc, 
desc.getClusterName());
 +
-         Map<String, List<String>> serviceURLs = new HashMap<>();
++        List<String> validServiceNames = new ArrayList<>();
++
++        Map<String, Map<String, String>> serviceParams = new HashMap<>();
++        Map<String, List<String>>        serviceURLs   = new HashMap<>();
 +
 +        if (cluster != null) {
 +            for (SimpleDescriptor.Service descService : desc.getServices()) {
 +                String serviceName = descService.getName();
 +
 +                List<String> descServiceURLs = descService.getURLs();
 +                if (descServiceURLs == null || descServiceURLs.isEmpty()) {
 +                    descServiceURLs = cluster.getServiceURLs(serviceName);
 +                }
 +
 +                // Validate the discovered service URLs
 +                List<String> validURLs = new ArrayList<>();
 +                if (descServiceURLs != null && !descServiceURLs.isEmpty()) {
 +                    // Validate the URL(s)
 +                    for (String descServiceURL : descServiceURLs) {
 +                        if (validateURL(serviceName, descServiceURL)) {
 +                            validURLs.add(descServiceURL);
 +                        }
 +                    }
++
++                    if (!validURLs.isEmpty()) {
++                        validServiceNames.add(serviceName);
++                    }
 +                }
 +
 +                // If there is at least one valid URL associated with the 
service, then add it to the map
 +                if (!validURLs.isEmpty()) {
 +                    serviceURLs.put(serviceName, validURLs);
 +                } else {
 +                    log.failedToDiscoverClusterServiceURLs(serviceName, 
cluster.getName());
 +                }
++
++                // Service params
++                if (descService.getParams() != null) {
++                    serviceParams.put(serviceName, descService.getParams());
++                    if (!validServiceNames.contains(serviceName)) {
++                        validServiceNames.add(serviceName);
++                    }
++                }
 +            }
 +        } else {
 +            log.failedToDiscoverClusterServices(desc.getClusterName());
 +        }
 +
 +        BufferedWriter fw = null;
 +        topologyDescriptor = null;
-         File providerConfig = null;
++        File providerConfig;
 +        try {
 +            // Verify that the referenced provider configuration exists 
before attempting to reading it
 +            providerConfig = 
resolveProviderConfigurationReference(desc.getProviderConfig(), srcDirectory);
 +            if (providerConfig == null) {
 +                
log.failedToResolveProviderConfigRef(desc.getProviderConfig());
 +                throw new IllegalArgumentException("Unresolved provider 
configuration reference: " +
 +                                                   desc.getProviderConfig() + 
" ; Topology update aborted!");
 +            }
 +            result.put("reference", providerConfig);
 +
 +            // TODO: Should the contents of the provider config be validated 
before incorporating it into the topology?
 +
 +            String topologyFilename = desc.getName();
 +            if (topologyFilename == null) {
 +                topologyFilename = desc.getClusterName();
 +            }
 +            topologyDescriptor = new File(destDirectory, topologyFilename + 
".xml");
 +            fw = new BufferedWriter(new FileWriter(topologyDescriptor));
 +
 +            fw.write("<topology>\n");
 +
 +            // Copy the externalized provider configuration content into the 
topology descriptor in-line
 +            InputStreamReader policyReader = new InputStreamReader(new 
FileInputStream(providerConfig));
 +            char[] buffer = new char[1024];
 +            int count;
 +            while ((count = policyReader.read(buffer)) > 0) {
 +                fw.write(buffer, 0, count);
 +            }
 +            policyReader.close();
 +
 +            // Sort the service names to write the services alphabetically
-             List<String> serviceNames = new ArrayList<>(serviceURLs.keySet());
++            List<String> serviceNames = new ArrayList<>(validServiceNames);
 +            Collections.sort(serviceNames);
 +
 +            // Write the service declarations
 +            for (String serviceName : serviceNames) {
 +                fw.write("    <service>\n");
 +                fw.write("        <role>" + serviceName + "</role>\n");
-                 for (String url : serviceURLs.get(serviceName)) {
-                     fw.write("        <url>" + url + "</url>\n");
++
++                // URLs
++                List<String> urls = serviceURLs.get(serviceName);
++                if (urls != null) {
++                    for (String url : urls) {
++                        fw.write("        <url>" + url + "</url>\n");
++                    }
 +                }
++
++                // Params
++                Map<String, String> svcParams = 
serviceParams.get(serviceName);
++                if (svcParams != null) {
++                    for (String paramName : svcParams.keySet()) {
++                        fw.write("        <param>\n");
++                        fw.write("            <name>" + paramName + 
"</name>\n");
++                        fw.write("            <value>" + 
svcParams.get(paramName) + "</value>\n");
++                        fw.write("        </param>\n");
++                    }
++                }
++
 +                fw.write("    </service>\n");
 +            }
 +
 +            fw.write("</topology>\n");
 +
 +            fw.flush();
 +        } catch (IOException e) {
 +            
log.failedToGenerateTopologyFromSimpleDescriptor(topologyDescriptor.getName(), 
e);
 +            topologyDescriptor.delete();
 +        } finally {
 +            if (fw != null) {
 +                try {
 +                    fw.close();
 +                } catch (IOException e) {
 +                    // ignore
 +                }
 +            }
 +        }
 +
 +        result.put("topology", topologyDescriptor);
 +        return result;
 +    }
 +
 +    private static boolean validateURL(String serviceName, String url) {
 +        boolean result = false;
 +
 +        if (url != null && !url.isEmpty()) {
 +            try {
 +                new URI(url);
 +                result = true;
 +            } catch (URISyntaxException e) {
 +                log.serviceURLValidationFailed(serviceName, url, e);
 +            }
 +        }
 +
 +        return result;
 +    }
 +
++
 +    private static File resolveProviderConfigurationReference(String 
reference, File srcDirectory) {
 +        File providerConfig;
 +
 +        // If the reference includes a path
 +        if (reference.contains(File.separator)) {
 +            // Check if it's an absolute path
 +            providerConfig = new File(reference);
 +            if (!providerConfig.exists()) {
 +                // If it's not an absolute path, try treating it as a 
relative path
 +                providerConfig = new File(srcDirectory, reference);
 +                if (!providerConfig.exists()) {
 +                    providerConfig = null;
 +                }
 +            }
 +        } else { // No file path, just a name
 +            // Check if it's co-located with the referencing descriptor
 +            providerConfig = new File(srcDirectory, reference);
 +            if (!providerConfig.exists()) {
 +                // Check the shared-providers config location
 +                File sharedProvidersDir = new File(srcDirectory, 
"../shared-providers");
 +                if (sharedProvidersDir.exists()) {
 +                    providerConfig = new File(sharedProvidersDir, reference);
 +                    if (!providerConfig.exists()) {
 +                        // Check if it's a valid name without the extension
 +                        providerConfig = new File(sharedProvidersDir, 
reference + ".xml");
 +                        if (!providerConfig.exists()) {
 +                            providerConfig = null;
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +
 +        return providerConfig;
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
----------------------------------------------------------------------
diff --cc 
gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
index 0ec7acf,0000000..4eb1954
mode 100644,000000..100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
@@@ -1,111 -1,0 +1,123 @@@
 +/**
 + * 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.knox.gateway.topology.simple;
 +
 +import com.fasterxml.jackson.annotation.JsonProperty;
 +
 +import java.util.ArrayList;
 +import java.util.List;
++import java.util.Map;
 +
 +class SimpleDescriptorImpl implements SimpleDescriptor {
 +
 +    @JsonProperty("discovery-type")
 +    private String discoveryType;
 +
 +    @JsonProperty("discovery-address")
 +    private String discoveryAddress;
 +
 +    @JsonProperty("discovery-user")
 +    private String discoveryUser;
 +
 +    @JsonProperty("discovery-pwd-alias")
 +    private String discoveryPasswordAlias;
 +
 +    @JsonProperty("provider-config-ref")
 +    private String providerConfig;
 +
 +    @JsonProperty("cluster")
 +    private String cluster;
 +
 +    @JsonProperty("services")
 +    private List<ServiceImpl> services;
 +
 +    private String name = null;
 +
 +    void setName(String name) {
 +        this.name = name;
 +    }
 +
 +    @Override
 +    public String getName() {
 +        return name;
 +    }
 +
 +    @Override
 +    public String getDiscoveryType() {
 +        return discoveryType;
 +    }
 +
 +    @Override
 +    public String getDiscoveryAddress() {
 +        return discoveryAddress;
 +    }
 +
 +    @Override
 +    public String getDiscoveryUser() {
 +        return discoveryUser;
 +    }
 +
 +    @Override
 +    public String getDiscoveryPasswordAlias() {
 +        return discoveryPasswordAlias;
 +    }
 +
 +    @Override
 +    public String getClusterName() {
 +        return cluster;
 +    }
 +
 +    @Override
 +    public String getProviderConfig() {
 +        return providerConfig;
 +    }
 +
 +    @Override
 +    public List<Service> getServices() {
 +        List<Service> result = new ArrayList<>();
 +        result.addAll(services);
 +        return result;
 +    }
 +
 +    public static class ServiceImpl implements Service {
++        @JsonProperty("name")
 +        private String name;
++
++        @JsonProperty("params")
++        private Map<String, String> params;
++
++        @JsonProperty("urls")
 +        private List<String> urls;
 +
 +        @Override
 +        public String getName() {
 +            return name;
 +        }
 +
 +        @Override
++        public Map<String, String> getParams() {
++            return params;
++        }
++
++        @Override
 +        public List<String> getURLs() {
 +            return urls;
 +        }
 +    }
 +
 +}

Reply via email to