You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/09/08 15:15:14 UTC

[21/24] knox git commit: Merge branch 'master' into KNOX-998-Package_Restructuring

http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-server/src/main/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
index 9ac71bd,0000000..a056ac7
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
@@@ -1,256 -1,0 +1,256 @@@
 +/**
 + * 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.deploy.impl;
 +
 +import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
 +import org.apache.knox.gateway.deploy.DeploymentContext;
 +import org.apache.knox.gateway.deploy.ServiceDeploymentContributorBase;
 +import org.apache.knox.gateway.descriptor.FilterDescriptor;
 +import org.apache.knox.gateway.descriptor.FilterParamDescriptor;
 +import org.apache.knox.gateway.descriptor.ResourceDescriptor;
 +import org.apache.knox.gateway.dispatch.GatewayDispatchFilter;
 +import org.apache.knox.gateway.filter.XForwardedHeaderFilter;
 +import org.apache.knox.gateway.filter.rewrite.api.CookieScopeServletFilter;
 +import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteRulesDescriptor;
 +import org.apache.knox.gateway.service.definition.CustomDispatch;
 +import org.apache.knox.gateway.service.definition.Policy;
 +import org.apache.knox.gateway.service.definition.Rewrite;
 +import org.apache.knox.gateway.service.definition.Route;
 +import org.apache.knox.gateway.service.definition.ServiceDefinition;
 +import org.apache.knox.gateway.topology.Provider;
 +import org.apache.knox.gateway.topology.Service;
 +import org.apache.knox.gateway.topology.Version;
 +
 +import java.net.URISyntaxException;
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +public class ServiceDefinitionDeploymentContributor extends ServiceDeploymentContributorBase {
 +
 +  private static final String DISPATCH_ROLE = "dispatch";
 +
 +  private static final String DISPATCH_IMPL_PARAM = "dispatch-impl";
 +
 +  private static final String HTTP_CLIENT_FACTORY_PARAM = "httpClientFactory";
 +
 +  private static final String SERVICE_ROLE_PARAM = "serviceRole";
 +
 +  private static final String XFORWARDED_FILTER_NAME = "XForwardedHeaderFilter";
 +
 +  private static final String XFORWARDED_FILTER_ROLE = "xforwardedheaders";
 +
 +  private static final String DEFAULT_HA_DISPATCH_CLASS = "org.apache.knox.gateway.ha.dispatch.DefaultHaDispatch";
 +
 +  private static final String COOKIE_SCOPING_FILTER_NAME = "CookieScopeServletFilter";
 +
 +  private static final String COOKIE_SCOPING_FILTER_ROLE = "cookiescopef";
 +
 +  private ServiceDefinition serviceDefinition;
 +
 +  private UrlRewriteRulesDescriptor serviceRules;
 +
 +  public ServiceDefinitionDeploymentContributor(ServiceDefinition serviceDefinition, UrlRewriteRulesDescriptor serviceRules) {
 +    this.serviceDefinition = serviceDefinition;
 +    this.serviceRules = serviceRules;
 +  }
 +
 +  @Override
 +  public String getRole() {
 +    return serviceDefinition.getRole();
 +  }
 +
 +  @Override
 +  public String getName() {
 +    return serviceDefinition.getName();
 +  }
 +
 +  @Override
 +  public Version getVersion() {
 +    return new Version(serviceDefinition.getVersion());
 +  }
 +
 +  @Override
 +  public void contributeService(DeploymentContext context, Service service) throws Exception {
 +    contributeRewriteRules(context, service);
 +    contributeResources(context, service);
 +  }
 +
 +  private void contributeRewriteRules(DeploymentContext context, Service service) {
 +    if ( serviceRules != null ) {
 +      UrlRewriteRulesDescriptor clusterRules = context.getDescriptor("rewrite");
 +      clusterRules.addRules(serviceRules);
 +    }
 +  }
 +
 +  private void contributeResources(DeploymentContext context, Service service) {
 +    Map<String, String> filterParams = new HashMap<>();
 +    List<Route> bindings = serviceDefinition.getRoutes();
 +    for ( Route binding : bindings ) {
 +      List<Rewrite> filters = binding.getRewrites();
 +      if ( filters != null && !filters.isEmpty() ) {
 +        filterParams.clear();
 +        for ( Rewrite filter : filters ) {
 +          filterParams.put(filter.getTo(), filter.getApply());
 +        }
 +      }
 +      try {
 +        contributeResource(context, service, binding, filterParams);
 +      } catch ( URISyntaxException e ) {
 +        e.printStackTrace();
 +      }
 +    }
 +
 +  }
 +
 +  private void contributeResource(DeploymentContext context, Service service, Route binding, Map<String, String> filterParams) throws URISyntaxException {
 +    List<FilterParamDescriptor> params = new ArrayList<FilterParamDescriptor>();
 +    ResourceDescriptor resource = context.getGatewayDescriptor().addResource();
 +    resource.role(service.getRole());
 +    resource.pattern(binding.getPath());
 +    //add x-forwarded filter if enabled in config
 +    if (context.getGatewayConfig().isXForwardedEnabled()) {
 +      resource.addFilter().name(XFORWARDED_FILTER_NAME).role(XFORWARDED_FILTER_ROLE).impl(XForwardedHeaderFilter.class);
 +    }
 +    if (context.getGatewayConfig().isCookieScopingToPathEnabled()) {
 +      FilterDescriptor filter = resource.addFilter().name(COOKIE_SCOPING_FILTER_NAME).role(COOKIE_SCOPING_FILTER_ROLE).impl(CookieScopeServletFilter.class);
 +      filter.param().name(GatewayConfigImpl.HTTP_PATH).value(context.getGatewayConfig().getGatewayPath());
 +    }
 +    List<Policy> policyBindings = binding.getPolicies();
 +    if ( policyBindings == null ) {
 +      policyBindings = serviceDefinition.getPolicies();
 +    }
 +    if ( policyBindings == null ) {
 +      //add default set
 +      addDefaultPolicies(context, service, filterParams, params, resource);
 +    } else {
 +      addPolicies(context, service, filterParams, params, resource, policyBindings);
 +    }
 +    addDispatchFilter(context, service, resource, binding);
 +  }
 +
 +  private void addPolicies(DeploymentContext context, Service service, Map<String, String> filterParams, List<FilterParamDescriptor> params, ResourceDescriptor resource, List<Policy> policyBindings) throws URISyntaxException {
 +    for ( Policy policyBinding : policyBindings ) {
 +      String role = policyBinding.getRole();
 +      if ( role == null ) {
 +        throw new IllegalArgumentException("Policy defined has no role for service " + service.getName());
 +      }
 +      role = role.trim().toLowerCase();
-       if ( role.equals("rewrite") ) {
++      if ( "rewrite".equals(role) ) {
 +        addRewriteFilter(context, service, filterParams, params, resource);
 +      } else if ( topologyContainsProviderType(context, role) ) {
 +        context.contributeFilter(service, resource, role, policyBinding.getName(), null);
 +      }
 +    }
 +  }
 +
 +  private void addDefaultPolicies(DeploymentContext context, Service service, Map<String, String> filterParams, List<FilterParamDescriptor> params, ResourceDescriptor resource) throws URISyntaxException {
 +    addWebAppSecFilters(context, service, resource);
 +    addAuthenticationFilter(context, service, resource);
 +    addRewriteFilter(context, service, filterParams, params, resource);
 +    addIdentityAssertionFilter(context, service, resource);
 +    addAuthorizationFilter(context, service, resource);
 +  }
 +
 +  private void addRewriteFilter(DeploymentContext context, Service service, Map<String, String> filterParams, List<FilterParamDescriptor> params, ResourceDescriptor resource) throws URISyntaxException {
 +    if ( !filterParams.isEmpty() ) {
 +      for ( Map.Entry<String, String> filterParam : filterParams.entrySet() ) {
 +        params.add(resource.createFilterParam().name(filterParam.getKey()).value(filterParam.getValue()));
 +      }
 +    }
 +    addRewriteFilter(context, service, resource, params);
 +  }
 +
 +  private void addDispatchFilter(DeploymentContext context, Service service, ResourceDescriptor resource, Route binding) {
 +    CustomDispatch customDispatch = binding.getDispatch();
 +    if ( customDispatch == null ) {
 +      customDispatch = serviceDefinition.getDispatch();
 +    }
 +    boolean isHaEnabled = isHaEnabled(context);
 +    if ( customDispatch != null ) {
 +      String haContributorName = customDispatch.getHaContributorName();
 +      String haClassName = customDispatch.getHaClassName();
 +      String httpClientFactory = customDispatch.getHttpClientFactory();
 +      if ( isHaEnabled) {
 +        if (haContributorName != null) {
 +          addDispatchFilter(context, service, resource, DISPATCH_ROLE, haContributorName);
 +        } else if (haClassName != null) {
 +          addDispatchFilterForClass(context, service, resource, haClassName, httpClientFactory);
 +        } else {
 +          addDefaultHaDispatchFilter(context, service, resource);
 +        }
 +      } else {
 +        String contributorName = customDispatch.getContributorName();
 +        if ( contributorName != null ) {
 +          addDispatchFilter(context, service, resource, DISPATCH_ROLE, contributorName);
 +        } else {
 +          String className = customDispatch.getClassName();
 +          if ( className != null ) {
 +            addDispatchFilterForClass(context, service, resource, className, httpClientFactory);
 +          } else {
 +            //final fallback to the default dispatch
 +            addDispatchFilter(context, service, resource, DISPATCH_ROLE, "http-client");
 +          }
 +        }
 +      }
 +    } else if (isHaEnabled) {
 +      addDefaultHaDispatchFilter(context, service, resource);
 +    } else {
 +      addDispatchFilter(context, service, resource, DISPATCH_ROLE, "http-client");
 +    }
 +  }
 +
 +  private void addDefaultHaDispatchFilter(DeploymentContext context, Service service, ResourceDescriptor resource) {
 +    FilterDescriptor filter = addDispatchFilterForClass(context, service, resource, DEFAULT_HA_DISPATCH_CLASS, null);
 +    filter.param().name(SERVICE_ROLE_PARAM).value(service.getRole());
 +  }
 +
 +  private FilterDescriptor addDispatchFilterForClass(DeploymentContext context, Service service, ResourceDescriptor resource, String dispatchClass, String httpClientFactory) {
 +    FilterDescriptor filter = resource.addFilter().name(getName()).role(DISPATCH_ROLE).impl(GatewayDispatchFilter.class);
 +    filter.param().name(DISPATCH_IMPL_PARAM).value(dispatchClass);
 +    if (httpClientFactory != null) {
 +      filter.param().name(HTTP_CLIENT_FACTORY_PARAM).value(httpClientFactory);
 +    }
 +    for ( Map.Entry<String, String> serviceParam : service.getParams().entrySet() ) {
 +      filter.param().name(serviceParam.getKey()).value(serviceParam.getValue());
 +    }
 +    if ( context.getGatewayConfig().isHadoopKerberosSecured() ) {
 +      filter.param().name("kerberos").value("true");
 +    } else {
 +      //TODO: [sumit] Get rid of special case. Add config/param capabilities to service definitions?
 +      //special case for hive
 +      filter.param().name("basicAuthPreemptive").value("true");
 +    }
 +    return filter;
 +  }
 +
 +  private boolean isHaEnabled(DeploymentContext context) {
 +    Provider provider = getProviderByRole(context, "ha");
 +    if ( provider != null && provider.isEnabled() ) {
 +      Map<String, String> params = provider.getParams();
 +      if ( params != null ) {
 +        if ( params.containsKey(getRole()) ) {
 +          return true;
 +        }
 +      }
 +    }
 +    return false;
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-server/src/main/java/org/apache/knox/gateway/dispatch/UrlConnectionDispatch.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/dispatch/UrlConnectionDispatch.java
index 0aa0063,0000000..ff40983
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/dispatch/UrlConnectionDispatch.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/dispatch/UrlConnectionDispatch.java
@@@ -1,163 -1,0 +1,163 @@@
 +/**
 + * 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.dispatch;
 +
 +import org.apache.commons.io.IOUtils;
 +import org.apache.knox.gateway.GatewayMessages;
 +import org.apache.knox.gateway.GatewayResources;
 +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.filter.AbstractGatewayFilter;
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
 +import org.apache.knox.gateway.util.urltemplate.Parser;
 +import org.apache.knox.gateway.util.urltemplate.Resolver;
 +import org.apache.knox.gateway.util.urltemplate.Rewriter;
 +import org.apache.knox.gateway.util.urltemplate.Template;
 +import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
 +import org.apache.hadoop.security.authentication.client.AuthenticationException;
 +import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
 +
 +import javax.servlet.FilterChain;
 +import javax.servlet.ServletException;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +
 +import java.io.FileNotFoundException;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.HttpURLConnection;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +import java.net.URL;
 +import java.net.URLEncoder;
 +import java.util.Enumeration;
 +
 +/**
 + *
 + */
 +public class UrlConnectionDispatch extends AbstractGatewayFilter {
 +
 +  private static final GatewayMessages LOG = MessagesFactory.get( GatewayMessages.class );
 +  private static final GatewayResources RES = ResourcesFactory.get( GatewayResources.class );
 +  private static Auditor auditor = AuditServiceFactory.getAuditService().getAuditor( AuditConstants.DEFAULT_AUDITOR_NAME,
 +          AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME );
 +
 +  @Override
 +  protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
 +    String method = request.getMethod().toUpperCase();
-     if (method.equals("GET")) {
++    if ("GET".equals(method)) {
 +      try {
 +        doGet(getDispatchUrl(request), request, response);
 +      } catch ( URISyntaxException e ) {
 +        throw new ServletException(e);
 +      }
 +    } else {
 +      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
 +    }
 +  }
 +
 +  protected static URI getDispatchUrl(HttpServletRequest request) {
 +    StringBuffer str = request.getRequestURL();
 +    String query = request.getQueryString();
 +    if ( query != null ) {
 +      str.append('?');
 +      str.append(query);
 +    }
 +    URI url = URI.create(str.toString());
 +    return url;
 +  }
 +
 +  public void doGet( URI url, HttpServletRequest request, HttpServletResponse response ) throws IOException, URISyntaxException {
 +    String sourcePathInfo = request.getPathInfo();
 +    String sourcePattern = getConfig().getInitParameter( "pattern" );
 +    String targetPattern = getConfig().getInitParameter( "target" );
 +
 +    //TODO: Some of the compilation should be done at servlet init for performance reasons.
 +    Template sourceTemplate = Parser.parseTemplate( sourcePattern );
 +    Template targetTemplate = Parser.parseTemplate( targetPattern );
 +
 +    Resolver resolver = new DispatchParamResolver( getConfig(), request );
 +    URI sourceUri = new URI( sourcePathInfo );
 +    URI targetUri = Rewriter.rewrite( sourceUri, sourceTemplate, targetTemplate, resolver, null );
 +
 +//    //TODO: This should be more at filter init.
 +//    Pattern sourceRegex = UrlRewriter.compileUrlRegex( sourcePattern );
 +//    Matcher matcher = sourceRegex.matcher( sourcePathInfo );
 +//    String targetUrl = MessageFormat.format( targetPattern, Regex.toGroupArray( matcher ) );
 +//    System.out.println( "Source URI: " + expect.getRequestURI() );
 +//    System.out.println( "Source URL: " + expect.getRequestURL() );
 +//    System.out.println( "Source Query: " + expect.getQueryString() );
 +//    System.out.println( "Source pathInfo: " + sourcePathInfo );
 +//    System.out.println( "Source pattern: " + sourcePattern );
 +//    System.out.println( "Target pattern: " + targetPattern );
 +//    System.out.println( "Resolved target: " + targetUrl );
 +
 +    StringBuilder paramStr = new StringBuilder();
 +    Enumeration paramNames = request.getParameterNames();
 +    if( paramNames.hasMoreElements() ) {
 +      paramStr.append( "?" );
 +    }
 +    while( paramNames.hasMoreElements() ) {
 +      String paramName = (String)paramNames.nextElement();
 +      String paramValue = request.getParameter( paramName );
 +      paramStr.append( paramName );
 +      paramStr.append( "=" );
 +      paramStr.append( URLEncoder.encode( paramValue, "UTF-8" ) );
 +      if( paramNames.hasMoreElements() ) {
 +        paramStr.append( "&" );
 +      }
 +    }
 +    String urlStr = targetUri.toString() + paramStr.toString();
 +    try {
 +      URL clientUrl = new URL( urlStr );
 +      //System.out.println( "Resolved query: " + clientUrl );
 +      AuthenticatedURL.Token token = new AuthenticatedURL.Token();
 +      KerberosAuthenticator authenticator = new KerberosAuthenticator();
 +      auditor.audit( Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.UNAVAILABLE );
 +      HttpURLConnection conn = new AuthenticatedURL( authenticator ).openConnection( clientUrl, token );
 +      //System.out.println( "STATUS=" + conn.getResponseCode() );
 +      InputStream input = conn.getInputStream();
 +      if( input != null ) {
 +        OutputStream output = response.getOutputStream();
 +        try {
 +          IOUtils.copy( input, output );
 +        } finally {
 +          //KNOX-685: output.flush();
 +          input.close();
 +        }
 +      }
 +      auditor.audit( Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.SUCCESS );
 +    } catch( AuthenticationException e ) {
 +      response.sendError( HttpServletResponse.SC_UNAUTHORIZED );
 +      LOG.failedToEstablishConnectionToUrl( urlStr, e );
 +      auditor.audit( Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.FAILURE, RES.responseStatus( HttpServletResponse.SC_UNAUTHORIZED ) );
 +    } catch( FileNotFoundException e ) {
 +      response.sendError( HttpServletResponse.SC_NOT_FOUND );
 +      LOG.failedToEstablishConnectionToUrl( urlStr, e );
 +      auditor.audit( Action.DISPATCH, urlStr, ResourceType.URI, ActionOutcome.FAILURE, RES.responseStatus( HttpServletResponse.SC_NOT_FOUND ) );
 +    }
 +
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/50f46e9e/gateway-server/src/main/java/org/apache/knox/gateway/topology/validation/TopologyValidator.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/validation/TopologyValidator.java
index 2c51535,0000000..1d3b00c
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/validation/TopologyValidator.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/validation/TopologyValidator.java
@@@ -1,122 -1,0 +1,122 @@@
 +/**
 + * 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.validation;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.net.URL;
 +import java.util.Collection;
 +import java.util.LinkedList;
 +import java.util.List;
 +import javax.xml.XMLConstants;
 +import javax.xml.transform.stream.StreamSource;
 +import javax.xml.validation.Schema;
 +import javax.xml.validation.SchemaFactory;
 +import javax.xml.validation.Validator;
 +
 +import org.apache.knox.gateway.topology.Topology;
 +import org.xml.sax.ErrorHandler;
 +import org.xml.sax.SAXException;
 +import org.xml.sax.SAXParseException;
 +
 +public class TopologyValidator {
 +
 +  private Collection<String> errors = null;
 +  private final String filePath;
 +
 +  public TopologyValidator(Topology t){
 +    filePath = t.getUri().getPath();
 +  }
 +
 +  public TopologyValidator(String path){
 +    this.filePath = path;
 +  }
 +
 +  public TopologyValidator(URL file){
 +    filePath = file.getPath();
 +  }
 +
 +  public boolean validateTopology() {
 +    errors = new LinkedList<String>();
 +    try {
 +      SchemaFactory fact = SchemaFactory
 +          .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 +      URL schemaUrl = getClass().getResource( "/conf/topology-v1.xsd" );
 +      Schema s = fact.newSchema( schemaUrl );
 +      Validator validator = s.newValidator();
 +      final List<SAXParseException> exceptions = new LinkedList<>();
 +      validator.setErrorHandler(new ErrorHandler() {
 +        public void warning(SAXParseException exception) throws SAXException {
 +          exceptions.add(exception);
 +        }
 +
 +        public void fatalError(SAXParseException exception) throws SAXException {
 +          exceptions.add(exception);
 +        }
 +
 +        public void error(SAXParseException exception) throws SAXException {
 +          exceptions.add(exception);
 +        }
 +      });
 +
 +      File xml = new File(filePath);
 +      validator.validate(new StreamSource(xml));
-       if(exceptions.size() > 0) {
++      if(!exceptions.isEmpty()) {
 +        for (SAXParseException e : exceptions) {
 +          errors.add("Line: " + e.getLineNumber() + " -- " + e.getMessage());
 +        }
 +        return false;
 +      } else {
 +        return true;
 +      }
 +
 +    } catch (IOException e) {
 +      errors.add("Error reading topology file");
 +      errors.add(e.getMessage());
 +      return false;
 +    } catch (SAXException e) {
 +      errors.add("There was a fatal error in parsing the xml file.");
 +      errors.add(e.getMessage());
 +      return false;
 +    } catch (NullPointerException n) {
 +      errors.add("Error retrieving schema from ClassLoader");
 +      return false;
 +    }
 +  }
 +
 +  public Collection<String> getTopologyErrors(){
 +
 +    if(errors != null){
 +      return errors;
 +    }else{
 +      validateTopology();
 +      return errors;
 +    }
 +  }
 +
 +  public String getErrorString(){
 +    StringBuilder out = new StringBuilder();
 +    out.append("");
 +    for(String s : getTopologyErrors()){
 +      out.append(s + "\n");
 +    }
 +    return out.toString();
 +  }
 +
 +}