0
\$\begingroup\$

Please critique my codebase https://github.com/davidbyng/acme-simple-servlet-extension

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce JSON HTTP content type.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AsJson {}

package com.acme.annotations;

import com.acme.authentication.LocalAuthentication;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce authentication.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {
  /**
   * Authentication class to use.
   *
   * @return Authentication class.
   */
  Class value() default LocalAuthentication.class;
}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce HTTP caching.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce HTTP content type.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentType {
  /**
   * Content type to enforce.
   *
   * @return Content type.
   */
  String value() default "text/hml";
}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Default handler for route class.
 */
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultRoute {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce HTTP GET method.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Disable HTTP caching.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface NoCache {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce HTTP POST method.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Post {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Disable authentication.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Public {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Add request token to output,
 * aids in preventing CSRF.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestToken {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Bind method to path.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Route {
  /**
   * Servlet path to bind to.
   *
   * @return Servlet path.
   */
  String value() default "";
}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Global filter to applicable to all routes.
 */
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface RouteFilter {}

package com.acme.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Enforce token validation,
 * aids in preventing CSRF.
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateToken {}

package com.acme.authentication;

import com.acme.authentication.types.AuthType;

public class AuthClass {
  private final AuthType authType;
  private final Object authObject;

  public AuthClass(AuthType authType, Object authObject) {
    this.authType = authType;
    this.authObject = authObject;
  }

  /**
   * Returns authentication URL to recirect to.
   *
   * @param context Context path of servlet.
   *
   * @return Authentication URL.
   */
  public String getAuthUrl(String context) {
    return authType == authType.LOCAL
        ? ((LocalAuthentication) authObject).getAuthUrl(context)
        : ((AzureAuthentication) authObject).getAuthUrl();
  }
}

package com.acme.authentication;

import java.io.InputStream;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class AzureAuthentication {
  private final Logger logger = LoggerFactory.getLogger(AzureAuthentication.class);
  private final Marker fatal = MarkerFactory.getMarker("FATAL");
  private Properties azureProperties;

  /**
   * Instantiate a new instance of Azure authentication.
   */
  public AzureAuthentication() {
    try {
      String configFile = "META-INF/authentication/azure.config";
      InputStream propsFiles = getClass().getClassLoader().getResourceAsStream(configFile);
      if (logger.isWarnEnabled() && propsFiles == null) {
        logger.warn("Can't load Azure config (" + configFile + ")");
      } else if (propsFiles != null) {
        azureProperties = new Properties();
        azureProperties.load(propsFiles);
        if (logger.isDebugEnabled()) {
          logger.debug("Azure authentication engine initalised");
        }
      }
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't initalise authentication engine", e);
      }
    }
  }

  /**
   * Get the authentication URL to redirect to.
   *
   * @return Authentication URL.
   */
  public String getAuthUrl() {
    return null;
  }
}

package com.acme.authentication;

import java.io.InputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Locale;
import java.util.Properties;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class LocalAuthentication {
  private final Logger logger = LoggerFactory.getLogger(LocalAuthentication.class);
  private final Marker fatal = MarkerFactory.getMarker("FATAL");
  private static final String algorithm = "PBKDF2WithHmacSHA1";
  private static final int derivedKeyLength = 160;
  private static final int iterations = 20000;
  private SecretKeyFactory encryptEngine = null;
  private SecureRandom randomByteGenerator = null;
  private Properties userList = null;

  /**
   * Instantiate a new instance of local authentication.
   */
  public LocalAuthentication() {
    try {
      String usersFile = "META-INF/authentication/users.db";
      InputStream propsFile = getClass().getClassLoader().getResourceAsStream(usersFile);
      if (logger.isWarnEnabled() && propsFile == null) {
        logger.warn("Can't load user list (" + usersFile + ")");
      } else if (propsFile != null) {
        userList = new Properties();
        userList.load(propsFile);
        if (logger.isDebugEnabled()) {
          logger.debug("Users list loaded (" + usersFile + ")");
        }
      }

      randomByteGenerator = SecureRandom.getInstance("SHA1PRNG");
      encryptEngine = SecretKeyFactory.getInstance(algorithm);
      if (logger.isDebugEnabled()) {
        logger.debug("Local authentication engine initalised");
      }
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't initalise local authentication engine", e);
      }
    }
  }

  public String getAuthUrl(String context) {
    return context != null ? context + "/auth" : "/auth";
  }

  /**
   * Perform authentication against user credentials.
   *
   * @param username User name credential
   * @param password Password credential
   * @return True when credentials match.
   */
  public boolean login(String username, String password) {
    boolean valid = false;

    if (userList != null) {
      String userPassword = userList.getProperty(username.toLowerCase(Locale.ENGLISH));
      if (userPassword != null) {
        if (logger.isDebugEnabled()) {
          logger.debug("User " + username + " found");
        }
        String[] parts = userPassword.split(":");
        if (parts != null && parts.length == 2) {
          byte[] salt = DatatypeConverter.parseBase64Binary(parts[0]);
          byte[] encryptedPassword = DatatypeConverter.parseBase64Binary(parts[1]);

          if (Arrays.equals(encryptPassword(password, salt), encryptedPassword)) {
            if (logger.isDebugEnabled()) {
              logger.debug("Credentials validated");
            }
            valid = true;
          } else if (logger.isInfoEnabled()) {
            logger.info("Invalid credentials");
          }
        }
      } else if (logger.isInfoEnabled()) {
        logger.info("User " + username + " not found");
      }
    } else if (logger.isErrorEnabled()) {
      logger.error(fatal, "User list not loaded");
    }

    return valid;
  }

  /**
   * Encrypt password credential.
   *
   * @param password Password credential.
   *
   * @return Encrypted password credential as base64.
   */
  public String encryptPassword(String password) {
    byte[] salt = generateSalt();
    return DatatypeConverter.printBase64Binary(salt)
        + ":"
        + DatatypeConverter.printBase64Binary(encryptPassword("pass", salt));
  }

  private byte[] encryptPassword(String password, byte[] salt) {
    byte[] encryptedPassword = null;

    try {
      KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength);
      encryptedPassword = encryptEngine.generateSecret(spec).getEncoded();
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't encrypt password", e);
      }
    }

    return encryptedPassword;
  }

  private byte[] generateSalt() {
    byte[] salt = null;
    try {
      salt = new byte[8];
      randomByteGenerator.nextBytes(salt);
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't generate salt", e);
      }
    }

    return salt;
  }
}

package com.acme.authentication.types;

public enum AuthType {
  LOCAL,
  AZURE;
}

package com.acme.dictionary;

public enum Language {
  INVALID,
  ENGLISH,
  FRENCH,
  GERMAN,
  SPANISH;
}

package com.acme.dictionary;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class LanguageDictionary {
  private final Logger logger = LoggerFactory.getLogger(LanguageDictionary.class);
  private final Marker fatal = MarkerFactory.getMarker("FATAL");
  private final Map<Language, Properties> languages = new HashMap<Language, Properties>();
  private Language defaultLanguage = Language.ENGLISH;
  private final List<String> allLanguages = new ArrayList<String>();

  /**
   * Instatiate a new language dictionary object.
   */
  public LanguageDictionary() {
    try {
      String configFile = "META-INF/dictionary/language.config";
      InputStream propsFile = getClass().getClassLoader().getResourceAsStream(configFile);
      if (logger.isWarnEnabled() && propsFile == null) {
        logger.warn("Can't load dictionary configuration (" + configFile + ")");
      } else if (propsFile != null) {
        Properties config = new Properties();
        config.load(propsFile);
        String configLanguage = config.getProperty("defaultLanguage");
        if (configLanguage != null) {
          this.defaultLanguage = Language.valueOf(configLanguage.toUpperCase(Locale.ENGLISH));
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Default language: "
                    + StringUtils.capitalize(configLanguage.toLowerCase(Locale.ENGLISH)));
          }
        }

        if (logger.isDebugEnabled()) {
          logger.debug("Dictionary configuration loaded (" + configFile + ")");
        }
      }

      for (Language language : Language.values()) {
        String langName = language.toString().toLowerCase(Locale.ENGLISH);
        String niceName = StringUtils.capitalize(langName);
        String dictName = "META-INF/dictionary/" + langName + ".lang";
        InputStream propsFiles = getClass().getClassLoader().getResourceAsStream(dictName);
        if (propsFiles == null) {
          if (logger.isWarnEnabled()) {
            logger.warn("Can't load " + niceName + " language dictionary (" + dictName + ")");
          }
          continue;
        }
        Properties dictionary = new Properties();
        dictionary.load(new InputStreamReader(propsFiles, Charset.forName("UTF-8")));
        languages.put(language, dictionary);
        if (logger.isDebugEnabled()) {
          logger.debug("Loaded " + niceName + " language dictionary (" + dictName + ")");
        }
        allLanguages.add(niceName);
      }

      if (logger.isDebugEnabled()) {
        logger.debug("Language dictionary initalised");
      }
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't initalise language dictionary", e);
      }
    }
  }

  /**
   * Retrieve language constant from dictionary.
   *
   * @param langConstant Language constant to lookup.
   * @param language Language dictionary to check.
   * @param defaultValue Default value when not found.
   * @return Constant in desrired language.
   */
  public String getConstant(String langConstant, Language language, String defaultValue) {
    String result = defaultValue;
    if (language == null) {
      language = defaultLanguage;
    }

    String niceName = StringUtils.capitalize(language.toString().toLowerCase(Locale.ENGLISH));
    Properties dictionary = languages.get(language);
    if (dictionary != null) {
      String found = dictionary.getProperty(langConstant);
      if (found != null) {
        if (logger.isDebugEnabled()) {
          logger.debug("Constant " + langConstant + " found in " + niceName + " dictionary");
        }
        result = found;
      } else if (logger.isWarnEnabled()) {
        logger.warn("Can't find constant " + langConstant + " in " + niceName + " dictionary");
      }
    } else if (logger.isWarnEnabled()) {
      logger.warn("Dictionary " + niceName + " not loaded");
    }

    return result;
  }

  public Language getDefaultLanguage() {
    return defaultLanguage;
  }

  public List<String> getLanguages() {
    return allLanguages;
  }
}

package com.acme.dictionary;

public class TemplateDictionary {
  private final LanguageDictionary languageDictionary;
  private final Language language;

  public TemplateDictionary(LanguageDictionary languageDictionary, Language language) {
    this.language = language;
    this.languageDictionary = languageDictionary;
  }

  public String get(String constantName, String defaultValue) {
    return languageDictionary.getConstant(constantName, language, defaultValue);
  }
}

package com.acme.encryption;

import com.acme.dictionary.Language;
import com.acme.http.Session;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.io.InputStream;
import java.security.Key;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class Jwt {
  private final Logger logger = LoggerFactory.getLogger(Jwt.class);
  private final Marker fatal = MarkerFactory.getMarker("FATAL");
  private Key jwtKey = null;

  /**
   * Instantiate a new instance of Jwt.
   */
  public Jwt() {
    try {
      String configFile = "META-INF/encryption/jwt.config";
      InputStream propsFile = getClass().getClassLoader().getResourceAsStream(configFile);
      if (logger.isWarnEnabled() && propsFile == null) {
        logger.warn("Can't load JWT config (" + configFile + ")");
      } else if (propsFile != null) {
        Properties properties = new Properties();
        properties.load(propsFile);
        jwtKey =
            Keys.hmacShaKeyFor(DatatypeConverter.parseBase64Binary(properties.getProperty("key")));
        if (logger.isDebugEnabled()) {
          logger.debug("JWT engine initalised");
        }
      }
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't initalise JWT engine", e);
      }
    }
  }

  /**
   * Sign session cookie.
   *
   * @param session HTTP session object.
   * @return Signed session.
   */
  public String signSession(Session session) {
    if (logger.isDebugEnabled()) {
      logger.debug("Signing session cookie");
    }
    return Jwts.builder()
        .claim("sessionId", session.getId())
        .claim("created", session.getCreated())
        .claim("updated", session.getUpdated())
        .claim("requestToken", session.getRequestToken())
        .claim("authToken", session.getAuthToken())
        .claim("language", session.getLanguage())
        .claim("returnUrl", session.getReturnUrl())
        .signWith(jwtKey)
        .compact();
  }

  /**
   * Decrypt session cookie.
   *
   * @param signedCookie Encrypted session cookie.
   * @return Users session.
   * @see Session
   */
  public Session getSession(String signedCookie) {
    Session session = null;
    try {
      Claims claims = Jwts.parser().setSigningKey(jwtKey).parseClaimsJws(signedCookie).getBody();
      if (claims != null && claims.get("sessionId") != null) {
        if (logger.isDebugEnabled()) {
          logger.debug("Decrypted signed session cookie");
        }
        Date created = new Date(TimeUnit.SECONDS.toMillis((Long) claims.get("created")));
        Date updated = new Date(TimeUnit.SECONDS.toMillis((Long) claims.get("updated")));
        session = new Session((String) claims.get("sessionId"), created, updated);
        if (claims.get("language") != null) {
          String language = (String) claims.get("language");
          if (logger.isDebugEnabled()) {
            logger.debug("Language: " + language);
          }
          session.setLanguage(Language.valueOf(language.toUpperCase(Locale.ENGLISH)));
        }

        if (claims.get("requestToken") != null) {
          if (logger.isDebugEnabled()) {
            logger.debug("Got request token");
          }
          session.setRequestToken((String) claims.get("requestToken"));
        }

        if (claims.get("authToken") != null) {
          if (logger.isDebugEnabled()) {
            logger.debug("Got authentication token");
          }
          session.setAuthToken((String) claims.get("authToken"));
        }

        if (claims.get("returnUrl") != null) {
          if (logger.isDebugEnabled()) {
            logger.debug("Got return URL");
          }
          session.setReturnUrl((String) claims.get("returnUrl"));
        }
      } else if (logger.isWarnEnabled()) {
        logger.warn("Can't decrypt signed session cookie");
      }
    } catch (JwtException e) {
      if (logger.isErrorEnabled()) {
        logger.error(fatal, "Can't decrypt session cookie", e);
      }
      session = null;
    }

    return session;
  }
}

package com.acme.format;

import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Json {
  private final Logger logger = LoggerFactory.getLogger(Json.class);
  private final Gson gson = new Gson();

  public Json() {
    logger.debug("Gson JSON format engine initalised");
  }

  public String toJson(Object object) {
    return gson.toJson(object);
  }
}

package com.acme.http;

import com.acme.http.types.RequestType;
import com.acme.http.types.ResponseType;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Event implements Serializable {
  private static final long serialVersionUID = 1L;

  private Object content;
  private String contentType = null;
  private Session session;
  private final Routes httpRoutes;
  private final RequestType requestType;
  private final HttpServletRequest request;
  private final HttpServletResponse response;
  private ResponseType responseType;
  private Map<String, String> routeParams = new LinkedHashMap<String, String>();

  /**
   * Instatiate new HTTP event object.
   *
   * @param requestType HTTP request type.
   * @param httpRoutes Route parsing object.
   * @param request HTTP request object.
   * @param response HTTP response object.
   * @param session HTTP session object.
   */
  public Event(
      RequestType requestType,
      Routes httpRoutes,
      HttpServletRequest request,
      HttpServletResponse response,
      Session session) {
    this.requestType = requestType;
    this.session = session;
    this.request = request;
    this.response = response;
    this.httpRoutes = httpRoutes;
  }

  public void setRoute(String base, String route) {
    routeParams = httpRoutes.getRouteParams(base, route, request.getServletPath());
  }

  public RequestType getType() {
    return requestType;
  }

  public HttpServletRequest getRequest() {
    return request;
  }

  public HttpServletResponse getResponse() {
    return response;
  }

  public Map<String, String> getRouteParams() {
    return routeParams;
  }

  public String getRouteParam(String name) {
    return routeParams.containsKey(name) ? routeParams.get(name) : null;
  }

  public void setContent(Object content) {
    this.content = content;
  }

  public Object getContent() {
    return content;
  }

  public void setContentType(String contentType) {
    this.contentType = contentType;
  }

  public String getContentType() {
    return contentType;
  }

  public void setSession(Session session) {
    this.session = session;
  }

  public Session getSession() {
    return session;
  }

  public void setResponseType(ResponseType responseType) {
    this.responseType = responseType;
  }

  public ResponseType getResponseType() {
    return responseType;
  }

  @Override
  public String toString() {
    return getContent() != null ? getContent().toString() : null;
  }
}

package com.acme.http;

import com.acme.http.types.ResponseType;
import java.io.Serializable;

public class Response implements Serializable {
  private static final long serialVersionUID = 1L;
  private final ResponseType responseType;
  private final Event event;

  /**
   * Instatiate new HTTP response object.
   *
   * @param responseType HTTP response status code.
   * @param event HTTP event object.
   */
  public Response(ResponseType responseType, Event event) {
    this.responseType = responseType;
    event.setResponseType(responseType);
    this.event = event;
  }

  public ResponseType getType() {
    return responseType;
  }

  public Event getEvent() {
    return event;
  }

  public Object getContent() {
    return event.getContent();
  }

  @Override
  public String toString() {
    return getContent() != null ? getContent().toString() : null;
  }
}

package com.acme.http;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Routes {
  private final Logger logger = LoggerFactory.getLogger(Routes.class);
  private static final Pattern findRouteParams = Pattern.compile("([^/]+)");
  private static final Pattern findParamsDef = Pattern.compile(":([^/]+)");

  /**
   * Instantiate a new instance of route parser.
   */
  public Routes() {
    if (logger.isDebugEnabled()) {
      logger.debug("HTTP route parser engine initalised");
    }
  }

  /**
   * Get REST parameters from HTTP request.
   *
   * @param baseRoute Context path of servlet
   * @param routeDef Route path definition
   * @param requestPath HTTP request path
   * @return Map of found parameters
   */
  public Map<String, String> getRouteParams(String baseRoute, String routeDef, String requestPath) {
    if (baseRoute != null) {
      routeDef = baseRoute + routeDef;
    }
    Map<String, String> routeParams = new LinkedHashMap<String, String>();
    Matcher defFound = findParamsDef.matcher(routeDef);
    while (defFound.find()) {
      routeParams.put(defFound.group(1), null);
      routeDef = routeDef.replaceAll(Pattern.quote("/:" + defFound.group(1)), "");
    }

    requestPath = requestPath.replaceAll(Pattern.quote(routeDef), "");
    Matcher paramsFound = findRouteParams.matcher(requestPath);

    for (String key : routeParams.keySet()) {
      paramsFound.find();
      if (logger.isDebugEnabled()) {
        logger.debug("Found route param: " + key + " value: " + paramsFound.group());
      }
      routeParams.put(key, paramsFound.group());
    }

    return routeParams;
  }

  /**
   * Matches HTTP request path against route definition.
   *
   * @param baseRoute Context path of servlet
   * @param routeDef Route path definition
   * @param requestPath HTTP request path
   * @return True if matched.
   */
  public boolean routeMatched(String baseRoute, String routeDef, String requestPath) {
    if (baseRoute != null) {
      routeDef = baseRoute + routeDef;
    }

    if (routeDef.equals(requestPath)) {
      if (logger.isDebugEnabled()) {
        logger.debug("Request path: " + requestPath + " matches route: " + routeDef);
      }
      return true;
    }

    String requestPathTemp = requestPath;
    String routeDefTemp = routeDef;
    Matcher defFound = findParamsDef.matcher(routeDef);
    ArrayList<String> routeParams = new ArrayList<String>();
    ArrayList<String> requestParams = new ArrayList<String>();

    while (defFound.find()) {
      routeParams.add(defFound.group(1));
      if (logger.isDebugEnabled()) {
        logger.debug("Found route param definition: " + defFound.group(1));
      }
      routeDefTemp = routeDefTemp.replaceAll(Pattern.quote("/:" + defFound.group(1)), "");
    }

    if (routeDef.contains(":") && requestPath.startsWith(routeDefTemp)) {
      requestPathTemp = requestPathTemp.replaceAll(Pattern.quote(routeDefTemp), "");
      Matcher paramsFound = findRouteParams.matcher(requestPathTemp);

      Iterator iterator = routeParams.iterator();
      while (iterator.hasNext()) {
        iterator.next();
        if (paramsFound.find()) {
          requestParams.add(paramsFound.group());
        }
      }

      if (!routeParams.isEmpty() && routeParams.size() == requestParams.size()) {
        if (logger.isDebugEnabled()) {
          logger.debug("Request path: " + requestPath + " matches route: " + routeDef);
        }
        return true;
      }
    }

    if (logger.isDebugEnabled()) {
      logger.debug("Request path: " + requestPath + " doesn't match route: " + routeDef);
    }

    return false;
  }
}

package com.acme.http;

import com.acme.dictionary.Language;
import java.io.Serializable;
import java.util.Date;
import java.util.Locale;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Session implements Serializable {
  private static final long serialVersionUID = 1L;
  private final Logger logger = LoggerFactory.getLogger(Session.class);

  private final String id;
  private Date created;
  private Date updated;
  private String returnUrl;
  private Language language;
  private String authToken = null;
  private String requestToken = null;

  public Session(String id) {
    this(id, new Date(), new Date());
  }

  public Session(String id, Date created) {
    this(id, created, new Date());
  }

  /**
   * Instantiate a new instance of HTTP session.
   *
   * @param id Session identifier.
   * @param created Date &amp; time of creation.
   * @param updated Date &amp; time of last update.
   */
  public Session(String id, Date created, Date updated) {
    this.id = id;
    this.created = created;
    this.updated = updated;
    this.language = Language.ENGLISH;
    if (logger.isDebugEnabled()) {
      logger.debug(
          "Default language: "
              + StringUtils.capitalize(language.toString().toLowerCase(Locale.ENGLISH)));
      logger.debug("New sesison created");
    }
  }

  public String getId() {
    return id;
  }

  public Date getCreated() {
    return created;
  }

  /**
   * Set the request token.
   *
   * @param requestToken Request token.
   */
  public void setRequestToken(String requestToken) {
    this.requestToken = requestToken;
    if (logger.isDebugEnabled()) {
      logger.debug("Request token added");
    }
    update();
  }

  public String getRequestToken() {
    return requestToken;
  }

  /**
   * Set the authorisation token.
   *
   * @param authToken Authorisation token.
   */
  public void setAuthToken(String authToken) {
    this.authToken = authToken;
    if (logger.isDebugEnabled()) {
      logger.debug("Auth token added");
    }
    update();
  }

  public String getAuthToken() {
    return authToken;
  }

  /**
   * Set the last updated date &amp; time.
   *
   * @param updated Date &amp; time.
   */
  public void setUpdated(Date updated) {
    this.updated = updated;
    if (logger.isDebugEnabled()) {
      logger.debug("Session updated");
    }
  }

  public Date getUpdated() {
    return updated;
  }

  /**
   * Set the last updated date &amp; time.
   */
  public void update() {
    setUpdated(new Date());
  }

  /**
   * Set the users language.
   *
   * @param language Language to set.
   */
  public void setLanguage(Language language) {
    if (logger.isDebugEnabled()) {
      logger.debug(
          "Language: " + StringUtils.capitalize(language.toString().toLowerCase(Locale.ENGLISH)));
    }
    this.language = language;
    update();
  }

  /**
   * Set the return URL after authenticating.
   *
   * @param returnUrl URL to return to.
   */
  public void setReturnUrl(String returnUrl) {
    if (logger.isDebugEnabled()) {
      logger.debug("URL to return to after login: " + returnUrl);
    }
    this.returnUrl = returnUrl;
    update();
  }

  public String getReturnUrl() {
    return returnUrl;
  }

  public Language getLanguage() {
    return language;
  }
}

I would appreciate feedback on security, code-smells and performance of this application.

\$\endgroup\$
  • 1
    \$\begingroup\$ "feedback on security" What is your threat model? \$\endgroup\$ – Mast Feb 7 at 16:42
  • 1
    \$\begingroup\$ This question is incomplete. What are the file names? Why did you write what you did? If the code is too large (that's quite an achievement by the way, our limit is more than twice that of Stack Overflow), it may simply be too big to be reviewed. Don't cut out the description to save on characters. We need context, part of that is a description of the how and why (unless it's absolutely clear from the code itself, which can't be said with this much code). \$\endgroup\$ – Mast Feb 7 at 16:45
  • \$\begingroup\$ This is 32 individual classes not 1 class, It would be MUCH simpler to just clone the github repository (but this seems to be against the rules) as the tests are also included in addition to original filenames. \$\endgroup\$ – David Byng Feb 7 at 16:48
  • \$\begingroup\$ You should decide on a subset of your project to have reviewed. Reviewing a whole application with 32 classes is a very large undertaking in the first place. In addition to that a lot of review concerns will usually duplicate across classes. A "cross section" of your application is often enough to give you a lot of insight into the state of your current code base. Please do include additional information about what your code does and supplemental information that can help reviewers to answer any specific questions you might have. \$\endgroup\$ – Vogel612 Feb 7 at 17:00
  • \$\begingroup\$ TBH I don't think I am going to get the help I need here :( I have now integrated PMD, spotbugs, checkstyle, jacoco & sonarcloud.io review into my workflow. \$\endgroup\$ – David Byng Feb 7 at 17:06

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.