src/main/java/de/uapcore/lightpit/AbstractServlet.java

changeset 179
623c340058f3
parent 168
1c3694ae224c
child 180
009700915269
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/main/java/de/uapcore/lightpit/AbstractServlet.java	Tue Jan 05 19:19:31 2021 +0100
     1.3 @@ -0,0 +1,471 @@
     1.4 +/*
     1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     1.6 + *
     1.7 + * Copyright 2018 Mike Becker. All rights reserved.
     1.8 + *
     1.9 + * Redistribution and use in source and binary forms, with or without
    1.10 + * modification, are permitted provided that the following conditions are met:
    1.11 + *
    1.12 + *   1. Redistributions of source code must retain the above copyright
    1.13 + *      notice, this list of conditions and the following disclaimer.
    1.14 + *
    1.15 + *   2. Redistributions in binary form must reproduce the above copyright
    1.16 + *      notice, this list of conditions and the following disclaimer in the
    1.17 + *      documentation and/or other materials provided with the distribution.
    1.18 + *
    1.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    1.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    1.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    1.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    1.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    1.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    1.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    1.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    1.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    1.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    1.29 + * POSSIBILITY OF SUCH DAMAGE.
    1.30 + *
    1.31 + */
    1.32 +package de.uapcore.lightpit;
    1.33 +
    1.34 +import de.uapcore.lightpit.dao.DataAccessObject;
    1.35 +import de.uapcore.lightpit.dao.PostgresDataAccessObject;
    1.36 +import org.slf4j.Logger;
    1.37 +import org.slf4j.LoggerFactory;
    1.38 +
    1.39 +import javax.servlet.ServletException;
    1.40 +import javax.servlet.http.HttpServlet;
    1.41 +import javax.servlet.http.HttpServletRequest;
    1.42 +import javax.servlet.http.HttpServletResponse;
    1.43 +import javax.servlet.http.HttpSession;
    1.44 +import java.io.IOException;
    1.45 +import java.lang.reflect.*;
    1.46 +import java.sql.Connection;
    1.47 +import java.sql.SQLException;
    1.48 +import java.util.*;
    1.49 +import java.util.function.Function;
    1.50 +import java.util.stream.Collectors;
    1.51 +
    1.52 +/**
    1.53 + * A special implementation of a HTTPServlet which is focused on implementing
    1.54 + * the necessary functionality for LightPIT pages.
    1.55 + */
    1.56 +public abstract class AbstractServlet extends HttpServlet {
    1.57 +
    1.58 +    private static final Logger LOG = LoggerFactory.getLogger(AbstractServlet.class);
    1.59 +
    1.60 +    private static final String SITE_JSP = jspPath("site");
    1.61 +
    1.62 +    /**
    1.63 +     * Invocation mapping gathered from the {@link RequestMapping} annotations.
    1.64 +     * <p>
    1.65 +     * Paths in this map must always start with a leading slash, although
    1.66 +     * the specification in the annotation must not start with a leading slash.
    1.67 +     * <p>
    1.68 +     * The reason for this is the different handling of empty paths in
    1.69 +     * {@link HttpServletRequest#getPathInfo()}.
    1.70 +     */
    1.71 +    private final Map<HttpMethod, Map<PathPattern, Method>> mappings = new HashMap<>();
    1.72 +
    1.73 +    /**
    1.74 +     * Returns the name of the resource bundle associated with this servlet.
    1.75 +     *
    1.76 +     * @return the resource bundle base name
    1.77 +     */
    1.78 +    protected abstract String getResourceBundleName();
    1.79 +
    1.80 +
    1.81 +    /**
    1.82 +     * Creates a set of data access objects for the specified connection.
    1.83 +     *
    1.84 +     * @param connection the SQL connection
    1.85 +     * @return a set of data access objects
    1.86 +     */
    1.87 +    private DataAccessObject createDataAccessObjects(Connection connection) {
    1.88 +        final var df = (DataSourceProvider) getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME());
    1.89 +        if (df.getDialect() == DataSourceProvider.Dialect.Postgres) {
    1.90 +            return new PostgresDataAccessObject(connection);
    1.91 +        }
    1.92 +        throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug.");
    1.93 +    }
    1.94 +
    1.95 +    private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException {
    1.96 +        final var pathPattern = mapping.getKey();
    1.97 +        final var method = mapping.getValue();
    1.98 +        try {
    1.99 +            LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName());
   1.100 +            final var paramTypes = method.getParameterTypes();
   1.101 +            final var paramValues = new Object[paramTypes.length];
   1.102 +            for (int i = 0; i < paramTypes.length; i++) {
   1.103 +                if (paramTypes[i].isAssignableFrom(HttpServletRequest.class)) {
   1.104 +                    paramValues[i] = req;
   1.105 +                } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) {
   1.106 +                    paramValues[i] = resp;
   1.107 +                }
   1.108 +                if (paramTypes[i].isAssignableFrom(DataAccessObject.class)) {
   1.109 +                    paramValues[i] = dao;
   1.110 +                }
   1.111 +                if (paramTypes[i].isAssignableFrom(PathParameters.class)) {
   1.112 +                    paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req));
   1.113 +                }
   1.114 +            }
   1.115 +            method.invoke(this, paramValues);
   1.116 +        } catch (InvocationTargetException ex) {
   1.117 +            LOG.error("invocation of method {}::{} failed: {}",
   1.118 +                    method.getDeclaringClass().getName(), method.getName(), ex.getTargetException().getMessage());
   1.119 +            LOG.debug("Details: ", ex.getTargetException());
   1.120 +            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getTargetException().getMessage());
   1.121 +        } catch (ReflectiveOperationException | ClassCastException ex) {
   1.122 +            LOG.error("invocation of method {}::{} failed: {}",
   1.123 +                    method.getDeclaringClass().getName(), method.getName(), ex.getMessage());
   1.124 +            LOG.debug("Details: ", ex);
   1.125 +            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
   1.126 +        }
   1.127 +    }
   1.128 +
   1.129 +    @Override
   1.130 +    public void init() throws ServletException {
   1.131 +        scanForRequestMappings();
   1.132 +
   1.133 +        LOG.trace("{} initialized", getServletName());
   1.134 +    }
   1.135 +
   1.136 +    private void scanForRequestMappings() {
   1.137 +        try {
   1.138 +            Method[] methods = getClass().getDeclaredMethods();
   1.139 +            for (Method method : methods) {
   1.140 +                Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
   1.141 +                if (mapping.isPresent()) {
   1.142 +                    if (mapping.get().requestPath().isBlank()) {
   1.143 +                        LOG.warn("{} is annotated with {} but request path is empty",
   1.144 +                                method.getName(), RequestMapping.class.getSimpleName()
   1.145 +                        );
   1.146 +                        continue;
   1.147 +                    }
   1.148 +
   1.149 +                    if (!Modifier.isPublic(method.getModifiers())) {
   1.150 +                        LOG.warn("{} is annotated with {} but is not public",
   1.151 +                                method.getName(), RequestMapping.class.getSimpleName()
   1.152 +                        );
   1.153 +                        continue;
   1.154 +                    }
   1.155 +                    if (Modifier.isAbstract(method.getModifiers())) {
   1.156 +                        LOG.warn("{} is annotated with {} but is abstract",
   1.157 +                                method.getName(), RequestMapping.class.getSimpleName()
   1.158 +                        );
   1.159 +                        continue;
   1.160 +                    }
   1.161 +
   1.162 +                    boolean paramsInjectible = true;
   1.163 +                    for (var param : method.getParameterTypes()) {
   1.164 +                        paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param)
   1.165 +                                            || HttpServletResponse.class.isAssignableFrom(param)
   1.166 +                                            || PathParameters.class.isAssignableFrom(param)
   1.167 +                                            || DataAccessObject.class.isAssignableFrom(param);
   1.168 +                    }
   1.169 +                    if (paramsInjectible) {
   1.170 +                        try {
   1.171 +                            PathPattern pathPattern = new PathPattern(mapping.get().requestPath());
   1.172 +
   1.173 +                            final var methodMappings = mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>());
   1.174 +                            final var currentMapping = methodMappings.putIfAbsent(pathPattern, method);
   1.175 +                            if (currentMapping != null) {
   1.176 +                                LOG.warn("Cannot map {} {} to {} in class {} - this would override the mapping to {}",
   1.177 +                                        mapping.get().method(),
   1.178 +                                        mapping.get().requestPath(),
   1.179 +                                        method.getName(),
   1.180 +                                        getClass().getSimpleName(),
   1.181 +                                        currentMapping.getName()
   1.182 +                                );
   1.183 +                            }
   1.184 +
   1.185 +                            LOG.debug("{} {} maps to {}::{}",
   1.186 +                                    mapping.get().method(),
   1.187 +                                    mapping.get().requestPath(),
   1.188 +                                    getClass().getSimpleName(),
   1.189 +                                    method.getName()
   1.190 +                            );
   1.191 +                        } catch (IllegalArgumentException ex) {
   1.192 +                            LOG.warn("Request mapping for {} failed: path pattern '{}' is syntactically invalid",
   1.193 +                                    method.getName(), mapping.get().requestPath()
   1.194 +                            );
   1.195 +                        }
   1.196 +                    } else {
   1.197 +                        LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest, HttpServletResponse, PathParameters, and DataAccessObjects are allowed",
   1.198 +                                method.getName(), RequestMapping.class.getSimpleName()
   1.199 +                        );
   1.200 +                    }
   1.201 +                }
   1.202 +            }
   1.203 +        } catch (SecurityException ex) {
   1.204 +            LOG.error("Scan for request mappings on declared methods failed.", ex);
   1.205 +        }
   1.206 +    }
   1.207 +
   1.208 +    @Override
   1.209 +    public void destroy() {
   1.210 +        mappings.clear();
   1.211 +        LOG.trace("{} destroyed", getServletName());
   1.212 +    }
   1.213 +
   1.214 +    /**
   1.215 +     * Sets the name of the content page.
   1.216 +     * <p>
   1.217 +     * It is sufficient to specify the name without any extension. The extension
   1.218 +     * is added automatically if not specified.
   1.219 +     *
   1.220 +     * @param req      the servlet request object
   1.221 +     * @param pageName the name of the content page
   1.222 +     * @see Constants#REQ_ATTR_CONTENT_PAGE
   1.223 +     */
   1.224 +    protected void setContentPage(HttpServletRequest req, String pageName) {
   1.225 +        req.setAttribute(Constants.REQ_ATTR_CONTENT_PAGE, jspPath(pageName));
   1.226 +    }
   1.227 +
   1.228 +    /**
   1.229 +     * Sets the navigation menu.
   1.230 +     *
   1.231 +     * @param req     the servlet request object
   1.232 +     * @param jspName the name of the menu's jsp file
   1.233 +     * @see Constants#REQ_ATTR_NAVIGATION
   1.234 +     */
   1.235 +    protected void setNavigationMenu(HttpServletRequest req, String jspName) {
   1.236 +        req.setAttribute(Constants.REQ_ATTR_NAVIGATION, jspPath(jspName));
   1.237 +    }
   1.238 +
   1.239 +    /**
   1.240 +     * @param req      the servlet request object
   1.241 +     * @param location the location where to redirect
   1.242 +     * @see Constants#REQ_ATTR_REDIRECT_LOCATION
   1.243 +     */
   1.244 +    protected void setRedirectLocation(HttpServletRequest req, String location) {
   1.245 +        if (location.startsWith("./")) {
   1.246 +            location = location.replaceFirst("\\./", baseHref(req));
   1.247 +        }
   1.248 +        req.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, location);
   1.249 +    }
   1.250 +
   1.251 +    /**
   1.252 +     * Specifies the names of additional stylesheets used by this Servlet.
   1.253 +     * <p>
   1.254 +     * It is sufficient to specify the name without any extension. The extension
   1.255 +     * is added automatically if not specified.
   1.256 +     *
   1.257 +     * @param req         the servlet request object
   1.258 +     * @param stylesheets the names of the stylesheets
   1.259 +     */
   1.260 +    public void setStylesheet(HttpServletRequest req, String ... stylesheets) {
   1.261 +        req.setAttribute(Constants.REQ_ATTR_STYLESHEET, Arrays
   1.262 +                .stream(stylesheets)
   1.263 +                .map(s -> enforceExt(s, ".css"))
   1.264 +                .collect(Collectors.toUnmodifiableList()));
   1.265 +    }
   1.266 +
   1.267 +    /**
   1.268 +     * Sets the view model object.
   1.269 +     * The type must match the expected type in the JSP file.
   1.270 +     *
   1.271 +     * @param req       the servlet request object
   1.272 +     * @param viewModel the view model object
   1.273 +     */
   1.274 +    public void setViewModel(HttpServletRequest req, Object viewModel) {
   1.275 +        req.setAttribute(Constants.REQ_ATTR_VIEWMODEL, viewModel);
   1.276 +    }
   1.277 +
   1.278 +    private <T> Optional<T> parseParameter(String paramValue, Class<T> clazz) {
   1.279 +        if (paramValue == null) return Optional.empty();
   1.280 +        if (clazz.equals(Boolean.class)) {
   1.281 +            if (paramValue.equalsIgnoreCase("false") || paramValue.equals("0")) {
   1.282 +                return Optional.of((T) Boolean.FALSE);
   1.283 +            } else {
   1.284 +                return Optional.of((T) Boolean.TRUE);
   1.285 +            }
   1.286 +        }
   1.287 +        if (clazz.equals(String.class)) return Optional.of((T) paramValue);
   1.288 +        if (java.sql.Date.class.isAssignableFrom(clazz)) {
   1.289 +            try {
   1.290 +                return Optional.of((T) java.sql.Date.valueOf(paramValue));
   1.291 +            } catch (IllegalArgumentException ex) {
   1.292 +                return Optional.empty();
   1.293 +            }
   1.294 +        }
   1.295 +        try {
   1.296 +            final Constructor<T> ctor = clazz.getConstructor(String.class);
   1.297 +            return Optional.of(ctor.newInstance(paramValue));
   1.298 +        } catch (ReflectiveOperationException e) {
   1.299 +            // does not type check and is not convertible - treat as if the parameter was never set
   1.300 +            return Optional.empty();
   1.301 +        }
   1.302 +    }
   1.303 +
   1.304 +    /**
   1.305 +     * Obtains a request parameter of the specified type.
   1.306 +     * The specified type must have a single-argument constructor accepting a string to perform conversion.
   1.307 +     * The constructor of the specified type may throw an exception on conversion failures.
   1.308 +     *
   1.309 +     * @param req   the servlet request object
   1.310 +     * @param clazz the class object of the expected type
   1.311 +     * @param name  the name of the parameter
   1.312 +     * @param <T>   the expected type
   1.313 +     * @return the parameter value or an empty optional, if no parameter with the specified name was found
   1.314 +     */
   1.315 +    protected <T> Optional<T> getParameter(HttpServletRequest req, Class<T> clazz, String name) {
   1.316 +        if (clazz.isArray()) {
   1.317 +            final String[] paramValues = req.getParameterValues(name);
   1.318 +            int len = paramValues == null ? 0 : paramValues.length;
   1.319 +            final var array = (T) Array.newInstance(clazz.getComponentType(), len);
   1.320 +            for (int i = 0; i < len; i++) {
   1.321 +                try {
   1.322 +                    final Constructor<?> ctor = clazz.getComponentType().getConstructor(String.class);
   1.323 +                    Array.set(array, i, ctor.newInstance(paramValues[i]));
   1.324 +                } catch (ReflectiveOperationException e) {
   1.325 +                    throw new RuntimeException(e);
   1.326 +                }
   1.327 +            }
   1.328 +            return Optional.of(array);
   1.329 +        } else {
   1.330 +            return parseParameter(req.getParameter(name), clazz);
   1.331 +        }
   1.332 +    }
   1.333 +
   1.334 +    /**
   1.335 +     * Tries to look up an entity with a key obtained from a request parameter.
   1.336 +     *
   1.337 +     * @param req   the servlet request object
   1.338 +     * @param clazz the class representing the type of the request parameter
   1.339 +     * @param name  the name of the request parameter
   1.340 +     * @param find  the find function (typically a DAO function)
   1.341 +     * @param <T>   the type of the request parameter
   1.342 +     * @param <R>   the type of the looked up entity
   1.343 +     * @return the retrieved entity or an empty optional if there is no such entity or the request parameter was missing
   1.344 +     * @throws SQLException if the find function throws an exception
   1.345 +     */
   1.346 +    protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, Function<? super T, ? extends R> find) {
   1.347 +        final var param = getParameter(req, clazz, name);
   1.348 +        if (param.isPresent()) {
   1.349 +            return Optional.ofNullable(find.apply(param.get()));
   1.350 +        } else {
   1.351 +            return Optional.empty();
   1.352 +        }
   1.353 +    }
   1.354 +
   1.355 +    protected void setAttributeFromParameter(HttpServletRequest req, String name) {
   1.356 +        final var parm = req.getParameter(name);
   1.357 +        if (parm != null) {
   1.358 +            req.setAttribute(name, parm);
   1.359 +        }
   1.360 +    }
   1.361 +
   1.362 +    private String sanitizeRequestPath(HttpServletRequest req) {
   1.363 +        return Optional.ofNullable(req.getPathInfo()).orElse("/");
   1.364 +    }
   1.365 +
   1.366 +    private Optional<Map.Entry<PathPattern, Method>> findMapping(HttpMethod method, HttpServletRequest req) {
   1.367 +        return Optional.ofNullable(mappings.get(method)).flatMap(rm ->
   1.368 +                rm.entrySet().stream().filter(
   1.369 +                        kv -> kv.getKey().matches(sanitizeRequestPath(req))
   1.370 +                ).findAny()
   1.371 +        );
   1.372 +    }
   1.373 +
   1.374 +    protected void renderSite(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
   1.375 +        req.getRequestDispatcher(SITE_JSP).forward(req, resp);
   1.376 +    }
   1.377 +
   1.378 +    protected Optional<String[]> availableLanguages() {
   1.379 +        return Optional.ofNullable(getServletContext().getInitParameter(Constants.CTX_ATTR_LANGUAGES)).map((x) -> x.split("\\s*,\\s*"));
   1.380 +    }
   1.381 +
   1.382 +    private static String baseHref(HttpServletRequest req) {
   1.383 +        return String.format("%s://%s:%d%s/",
   1.384 +                req.getScheme(),
   1.385 +                req.getServerName(),
   1.386 +                req.getServerPort(),
   1.387 +                req.getContextPath());
   1.388 +    }
   1.389 +
   1.390 +    private static String enforceExt(String filename, String ext) {
   1.391 +        return filename.endsWith(ext) ? filename : filename + ext;
   1.392 +    }
   1.393 +
   1.394 +    private static String jspPath(String filename) {
   1.395 +        return enforceExt(Constants.JSP_PATH_PREFIX + filename, ".jsp");
   1.396 +    }
   1.397 +
   1.398 +    private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
   1.399 +        // the very first thing to do is to force UTF-8
   1.400 +        req.setCharacterEncoding("UTF-8");
   1.401 +
   1.402 +        // choose the requested language as session language (if available) or fall back to english, otherwise
   1.403 +        HttpSession session = req.getSession();
   1.404 +        if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
   1.405 +            Optional<List<String>> availableLanguages = availableLanguages().map(Arrays::asList);
   1.406 +            Optional<Locale> reqLocale = Optional.of(req.getLocale());
   1.407 +            Locale sessionLocale = reqLocale.filter((rl) -> availableLanguages.map((al) -> al.contains(rl.getLanguage())).orElse(false)).orElse(Locale.ENGLISH);
   1.408 +            session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale);
   1.409 +            LOG.debug("Setting language for new session {}: {}", session.getId(), sessionLocale.getDisplayLanguage());
   1.410 +        } else {
   1.411 +            Locale sessionLocale = (Locale) session.getAttribute(Constants.SESSION_ATTR_LANGUAGE);
   1.412 +            resp.setLocale(sessionLocale);
   1.413 +            LOG.trace("Continuing session {} with language {}", session.getId(), sessionLocale);
   1.414 +        }
   1.415 +
   1.416 +        // set some internal request attributes
   1.417 +        final String fullPath = req.getServletPath() + Optional.ofNullable(req.getPathInfo()).orElse("");
   1.418 +        req.setAttribute(Constants.REQ_ATTR_BASE_HREF, baseHref(req));
   1.419 +        req.setAttribute(Constants.REQ_ATTR_PATH, fullPath);
   1.420 +        req.setAttribute(Constants.REQ_ATTR_RESOURCE_BUNDLE, getResourceBundleName());
   1.421 +
   1.422 +        // if this is an error path, bypass the normal flow
   1.423 +        if (fullPath.startsWith("/error/")) {
   1.424 +            final var mapping = findMapping(method, req);
   1.425 +            if (mapping.isPresent()) {
   1.426 +                invokeMapping(mapping.get(), req, resp, null);
   1.427 +            }
   1.428 +            return;
   1.429 +        }
   1.430 +
   1.431 +        // obtain a connection and create the data access objects
   1.432 +        final var db = (DataSourceProvider) req.getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME());
   1.433 +        final var ds = db.getDataSource();
   1.434 +        if (ds == null) {
   1.435 +            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "JNDI DataSource lookup failed. See log for details.");
   1.436 +            return;
   1.437 +        }
   1.438 +        try (final var connection = ds.getConnection()) {
   1.439 +            final var dao = createDataAccessObjects(connection);
   1.440 +            try {
   1.441 +                connection.setAutoCommit(false);
   1.442 +                // call the handler, if available, or send an HTTP 404 error
   1.443 +                final var mapping = findMapping(method, req);
   1.444 +                if (mapping.isPresent()) {
   1.445 +                    invokeMapping(mapping.get(), req, resp, dao);
   1.446 +                } else {
   1.447 +                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   1.448 +                }
   1.449 +                connection.commit();
   1.450 +            } catch (SQLException ex) {
   1.451 +                LOG.warn("Database transaction failed (Code {}): {}", ex.getErrorCode(), ex.getMessage());
   1.452 +                LOG.debug("Details: ", ex);
   1.453 +                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unhandled Transaction Error - Code: " + ex.getErrorCode());
   1.454 +                connection.rollback();
   1.455 +            }
   1.456 +        } catch (SQLException ex) {
   1.457 +            LOG.error("Severe Database Exception (Code {}): {}", ex.getErrorCode(), ex.getMessage());
   1.458 +            LOG.debug("Details: ", ex);
   1.459 +            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.getErrorCode());
   1.460 +        }
   1.461 +    }
   1.462 +
   1.463 +    @Override
   1.464 +    protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
   1.465 +            throws ServletException, IOException {
   1.466 +        doProcess(HttpMethod.GET, req, resp);
   1.467 +    }
   1.468 +
   1.469 +    @Override
   1.470 +    protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
   1.471 +            throws ServletException, IOException {
   1.472 +        doProcess(HttpMethod.POST, req, resp);
   1.473 +    }
   1.474 +}

mercurial