Fri, 02 Apr 2021 11:59:14 +0200
completes kotlin migration
1.1 --- a/src/main/java/de/uapcore/lightpit/AbstractServlet.java Sat Jan 23 14:47:59 2021 +0100 1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 @@ -1,460 +0,0 @@ 1.4 -/* 1.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 1.6 - * 1.7 - * Copyright 2021 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 - /** 1.61 - * Invocation mapping gathered from the {@link RequestMapping} annotations. 1.62 - * <p> 1.63 - * Paths in this map must always start with a leading slash, although 1.64 - * the specification in the annotation must not start with a leading slash. 1.65 - * <p> 1.66 - * The reason for this is the different handling of empty paths in 1.67 - * {@link HttpServletRequest#getPathInfo()}. 1.68 - */ 1.69 - private final Map<HttpMethod, Map<PathPattern, Method>> mappings = new HashMap<>(); 1.70 - 1.71 - /** 1.72 - * Creates a set of data access objects for the specified connection. 1.73 - * 1.74 - * @param connection the SQL connection 1.75 - * @return a set of data access objects 1.76 - */ 1.77 - private DataAccessObject createDataAccessObjects(Connection connection) { 1.78 - final var df = (DataSourceProvider) getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME()); 1.79 - if (df.getDialect() == DataSourceProvider.Dialect.Postgres) { 1.80 - return new PostgresDataAccessObject(connection); 1.81 - } 1.82 - throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug."); 1.83 - } 1.84 - 1.85 - private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException { 1.86 - final var pathPattern = mapping.getKey(); 1.87 - final var method = mapping.getValue(); 1.88 - try { 1.89 - LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); 1.90 - final var paramTypes = method.getParameterTypes(); 1.91 - final var paramValues = new Object[paramTypes.length]; 1.92 - for (int i = 0; i < paramTypes.length; i++) { 1.93 - if (paramTypes[i].isAssignableFrom(HttpServletRequest.class)) { 1.94 - paramValues[i] = req; 1.95 - } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) { 1.96 - paramValues[i] = resp; 1.97 - } 1.98 - if (paramTypes[i].isAssignableFrom(DataAccessObject.class)) { 1.99 - paramValues[i] = dao; 1.100 - } 1.101 - if (paramTypes[i].isAssignableFrom(PathParameters.class)) { 1.102 - paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req)); 1.103 - } 1.104 - } 1.105 - method.invoke(this, paramValues); 1.106 - } catch (InvocationTargetException ex) { 1.107 - LOG.error("invocation of method {}::{} failed: {}", 1.108 - method.getDeclaringClass().getName(), method.getName(), ex.getTargetException().getMessage()); 1.109 - LOG.debug("Details: ", ex.getTargetException()); 1.110 - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getTargetException().getMessage()); 1.111 - } catch (ReflectiveOperationException | ClassCastException ex) { 1.112 - LOG.error("invocation of method {}::{} failed: {}", 1.113 - method.getDeclaringClass().getName(), method.getName(), ex.getMessage()); 1.114 - LOG.debug("Details: ", ex); 1.115 - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); 1.116 - } 1.117 - } 1.118 - 1.119 - @Override 1.120 - public void init() throws ServletException { 1.121 - scanForRequestMappings(); 1.122 - 1.123 - LOG.trace("{} initialized", getServletName()); 1.124 - } 1.125 - 1.126 - private void scanForRequestMappings() { 1.127 - try { 1.128 - Method[] methods = getClass().getDeclaredMethods(); 1.129 - for (Method method : methods) { 1.130 - Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); 1.131 - if (mapping.isPresent()) { 1.132 - if (mapping.get().requestPath().isBlank()) { 1.133 - LOG.warn("{} is annotated with {} but request path is empty", 1.134 - method.getName(), RequestMapping.class.getSimpleName() 1.135 - ); 1.136 - continue; 1.137 - } 1.138 - 1.139 - if (!Modifier.isPublic(method.getModifiers())) { 1.140 - LOG.warn("{} is annotated with {} but is not public", 1.141 - method.getName(), RequestMapping.class.getSimpleName() 1.142 - ); 1.143 - continue; 1.144 - } 1.145 - if (Modifier.isAbstract(method.getModifiers())) { 1.146 - LOG.warn("{} is annotated with {} but is abstract", 1.147 - method.getName(), RequestMapping.class.getSimpleName() 1.148 - ); 1.149 - continue; 1.150 - } 1.151 - 1.152 - boolean paramsInjectible = true; 1.153 - for (var param : method.getParameterTypes()) { 1.154 - paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param) 1.155 - || HttpServletResponse.class.isAssignableFrom(param) 1.156 - || PathParameters.class.isAssignableFrom(param) 1.157 - || DataAccessObject.class.isAssignableFrom(param); 1.158 - } 1.159 - if (paramsInjectible) { 1.160 - try { 1.161 - PathPattern pathPattern = new PathPattern(mapping.get().requestPath()); 1.162 - 1.163 - final var methodMappings = mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()); 1.164 - final var currentMapping = methodMappings.putIfAbsent(pathPattern, method); 1.165 - if (currentMapping != null) { 1.166 - LOG.warn("Cannot map {} {} to {} in class {} - this would override the mapping to {}", 1.167 - mapping.get().method(), 1.168 - mapping.get().requestPath(), 1.169 - method.getName(), 1.170 - getClass().getSimpleName(), 1.171 - currentMapping.getName() 1.172 - ); 1.173 - } 1.174 - 1.175 - LOG.debug("{} {} maps to {}::{}", 1.176 - mapping.get().method(), 1.177 - mapping.get().requestPath(), 1.178 - getClass().getSimpleName(), 1.179 - method.getName() 1.180 - ); 1.181 - } catch (IllegalArgumentException ex) { 1.182 - LOG.warn("Request mapping for {} failed: path pattern '{}' is syntactically invalid", 1.183 - method.getName(), mapping.get().requestPath() 1.184 - ); 1.185 - } 1.186 - } else { 1.187 - LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest, HttpServletResponse, PathParameters, and DataAccessObjects are allowed", 1.188 - method.getName(), RequestMapping.class.getSimpleName() 1.189 - ); 1.190 - } 1.191 - } 1.192 - } 1.193 - } catch (SecurityException ex) { 1.194 - LOG.error("Scan for request mappings on declared methods failed.", ex); 1.195 - } 1.196 - } 1.197 - 1.198 - @Override 1.199 - public void destroy() { 1.200 - mappings.clear(); 1.201 - LOG.trace("{} destroyed", getServletName()); 1.202 - } 1.203 - 1.204 - /** 1.205 - * Sets the name of the content page. 1.206 - * <p> 1.207 - * It is sufficient to specify the name without any extension. The extension 1.208 - * is added automatically if not specified. 1.209 - * 1.210 - * @param req the servlet request object 1.211 - * @param pageName the name of the content page 1.212 - * @see Constants#REQ_ATTR_CONTENT_PAGE 1.213 - */ 1.214 - protected void setContentPage(HttpServletRequest req, String pageName) { 1.215 - req.setAttribute(Constants.REQ_ATTR_CONTENT_PAGE, jspPath(pageName)); 1.216 - } 1.217 - 1.218 - /** 1.219 - * Sets the navigation menu. 1.220 - * 1.221 - * @param req the servlet request object 1.222 - * @param jspName the name of the menu's jsp file 1.223 - * @see Constants#REQ_ATTR_NAVIGATION 1.224 - */ 1.225 - protected void setNavigationMenu(HttpServletRequest req, String jspName) { 1.226 - req.setAttribute(Constants.REQ_ATTR_NAVIGATION, jspPath(jspName)); 1.227 - } 1.228 - 1.229 - /** 1.230 - * @param req the servlet request object 1.231 - * @param location the location where to redirect 1.232 - * @see Constants#REQ_ATTR_REDIRECT_LOCATION 1.233 - */ 1.234 - protected void setRedirectLocation(HttpServletRequest req, String location) { 1.235 - if (location.startsWith("./")) { 1.236 - location = location.replaceFirst("\\./", baseHref(req)); 1.237 - } 1.238 - req.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, location); 1.239 - } 1.240 - 1.241 - /** 1.242 - * Specifies the names of additional stylesheets used by this Servlet. 1.243 - * <p> 1.244 - * It is sufficient to specify the name without any extension. The extension 1.245 - * is added automatically if not specified. 1.246 - * 1.247 - * @param req the servlet request object 1.248 - * @param stylesheets the names of the stylesheets 1.249 - */ 1.250 - public void setStylesheet(HttpServletRequest req, String ... stylesheets) { 1.251 - req.setAttribute(Constants.REQ_ATTR_STYLESHEET, Arrays 1.252 - .stream(stylesheets) 1.253 - .map(s -> enforceExt(s, ".css")) 1.254 - .collect(Collectors.toUnmodifiableList())); 1.255 - } 1.256 - 1.257 - /** 1.258 - * Sets the view model object. 1.259 - * The type must match the expected type in the JSP file. 1.260 - * 1.261 - * @param req the servlet request object 1.262 - * @param viewModel the view model object 1.263 - */ 1.264 - public void setViewModel(HttpServletRequest req, Object viewModel) { 1.265 - req.setAttribute(Constants.REQ_ATTR_VIEWMODEL, viewModel); 1.266 - } 1.267 - 1.268 - private <T> Optional<T> parseParameter(String paramValue, Class<T> clazz) { 1.269 - if (paramValue == null) return Optional.empty(); 1.270 - if (clazz.equals(Boolean.class)) { 1.271 - if (paramValue.equalsIgnoreCase("false") || paramValue.equals("0")) { 1.272 - return Optional.of((T) Boolean.FALSE); 1.273 - } else { 1.274 - return Optional.of((T) Boolean.TRUE); 1.275 - } 1.276 - } 1.277 - if (clazz.equals(String.class)) return Optional.of((T) paramValue); 1.278 - if (java.sql.Date.class.isAssignableFrom(clazz)) { 1.279 - try { 1.280 - return Optional.of((T) java.sql.Date.valueOf(paramValue)); 1.281 - } catch (IllegalArgumentException ex) { 1.282 - return Optional.empty(); 1.283 - } 1.284 - } 1.285 - try { 1.286 - final Constructor<T> ctor = clazz.getConstructor(String.class); 1.287 - return Optional.of(ctor.newInstance(paramValue)); 1.288 - } catch (ReflectiveOperationException e) { 1.289 - // does not type check and is not convertible - treat as if the parameter was never set 1.290 - return Optional.empty(); 1.291 - } 1.292 - } 1.293 - 1.294 - /** 1.295 - * Obtains a request parameter of the specified type. 1.296 - * The specified type must have a single-argument constructor accepting a string to perform conversion. 1.297 - * The constructor of the specified type may throw an exception on conversion failures. 1.298 - * 1.299 - * @param req the servlet request object 1.300 - * @param clazz the class object of the expected type 1.301 - * @param name the name of the parameter 1.302 - * @param <T> the expected type 1.303 - * @return the parameter value or an empty optional, if no parameter with the specified name was found 1.304 - */ 1.305 - protected <T> Optional<T> getParameter(HttpServletRequest req, Class<T> clazz, String name) { 1.306 - if (clazz.isArray()) { 1.307 - final String[] paramValues = req.getParameterValues(name); 1.308 - int len = paramValues == null ? 0 : paramValues.length; 1.309 - final var array = (T) Array.newInstance(clazz.getComponentType(), len); 1.310 - for (int i = 0; i < len; i++) { 1.311 - try { 1.312 - final Constructor<?> ctor = clazz.getComponentType().getConstructor(String.class); 1.313 - Array.set(array, i, ctor.newInstance(paramValues[i])); 1.314 - } catch (ReflectiveOperationException e) { 1.315 - throw new RuntimeException(e); 1.316 - } 1.317 - } 1.318 - return Optional.of(array); 1.319 - } else { 1.320 - return parseParameter(req.getParameter(name), clazz); 1.321 - } 1.322 - } 1.323 - 1.324 - /** 1.325 - * Tries to look up an entity with a key obtained from a request parameter. 1.326 - * 1.327 - * @param req the servlet request object 1.328 - * @param clazz the class representing the type of the request parameter 1.329 - * @param name the name of the request parameter 1.330 - * @param find the find function (typically a DAO function) 1.331 - * @param <T> the type of the request parameter 1.332 - * @param <R> the type of the looked up entity 1.333 - * @return the retrieved entity or an empty optional if there is no such entity or the request parameter was missing 1.334 - * @throws SQLException if the find function throws an exception 1.335 - */ 1.336 - protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, Function<? super T, ? extends R> find) { 1.337 - final var param = getParameter(req, clazz, name); 1.338 - if (param.isPresent()) { 1.339 - return Optional.ofNullable(find.apply(param.get())); 1.340 - } else { 1.341 - return Optional.empty(); 1.342 - } 1.343 - } 1.344 - 1.345 - protected void setAttributeFromParameter(HttpServletRequest req, String name) { 1.346 - final var parm = req.getParameter(name); 1.347 - if (parm != null) { 1.348 - req.setAttribute(name, parm); 1.349 - } 1.350 - } 1.351 - 1.352 - private String sanitizeRequestPath(HttpServletRequest req) { 1.353 - return Optional.ofNullable(req.getPathInfo()).orElse("/"); 1.354 - } 1.355 - 1.356 - private Optional<Map.Entry<PathPattern, Method>> findMapping(HttpMethod method, HttpServletRequest req) { 1.357 - return Optional.ofNullable(mappings.get(method)).flatMap(rm -> 1.358 - rm.entrySet().stream().filter( 1.359 - kv -> kv.getKey().matches(sanitizeRequestPath(req)) 1.360 - ).findAny() 1.361 - ); 1.362 - } 1.363 - 1.364 - protected void renderSite(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 1.365 - req.getRequestDispatcher(jspPath("site")).forward(req, resp); 1.366 - } 1.367 - 1.368 - protected Optional<String[]> availableLanguages() { 1.369 - return Optional.ofNullable(getServletContext().getInitParameter(Constants.CTX_ATTR_LANGUAGES)).map((x) -> x.split("\\s*,\\s*")); 1.370 - } 1.371 - 1.372 - private static String baseHref(HttpServletRequest req) { 1.373 - return String.format("%s://%s:%d%s/", 1.374 - req.getScheme(), 1.375 - req.getServerName(), 1.376 - req.getServerPort(), 1.377 - req.getContextPath()); 1.378 - } 1.379 - 1.380 - private static String enforceExt(String filename, String ext) { 1.381 - return filename.endsWith(ext) ? filename : filename + ext; 1.382 - } 1.383 - 1.384 - private static String jspPath(String filename) { 1.385 - return enforceExt(Constants.JSP_PATH_PREFIX + filename, ".jsp"); 1.386 - } 1.387 - 1.388 - private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 1.389 - // the very first thing to do is to force UTF-8 1.390 - req.setCharacterEncoding("UTF-8"); 1.391 - 1.392 - // choose the requested language as session language (if available) or fall back to english, otherwise 1.393 - HttpSession session = req.getSession(); 1.394 - if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { 1.395 - Optional<List<String>> availableLanguages = availableLanguages().map(Arrays::asList); 1.396 - Optional<Locale> reqLocale = Optional.of(req.getLocale()); 1.397 - Locale sessionLocale = reqLocale.filter((rl) -> availableLanguages.map((al) -> al.contains(rl.getLanguage())).orElse(false)).orElse(Locale.ENGLISH); 1.398 - session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale); 1.399 - LOG.debug("Setting language for new session {}: {}", session.getId(), sessionLocale.getDisplayLanguage()); 1.400 - } else { 1.401 - Locale sessionLocale = (Locale) session.getAttribute(Constants.SESSION_ATTR_LANGUAGE); 1.402 - resp.setLocale(sessionLocale); 1.403 - LOG.trace("Continuing session {} with language {}", session.getId(), sessionLocale); 1.404 - } 1.405 - 1.406 - // set some internal request attributes 1.407 - final String fullPath = req.getServletPath() + Optional.ofNullable(req.getPathInfo()).orElse(""); 1.408 - req.setAttribute(Constants.REQ_ATTR_BASE_HREF, baseHref(req)); 1.409 - req.setAttribute(Constants.REQ_ATTR_PATH, fullPath); 1.410 - 1.411 - // if this is an error path, bypass the normal flow 1.412 - if (fullPath.startsWith("/error/")) { 1.413 - final var mapping = findMapping(method, req); 1.414 - if (mapping.isPresent()) { 1.415 - invokeMapping(mapping.get(), req, resp, null); 1.416 - } 1.417 - return; 1.418 - } 1.419 - 1.420 - // obtain a connection and create the data access objects 1.421 - final var db = (DataSourceProvider) req.getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME()); 1.422 - final var ds = db.getDataSource(); 1.423 - if (ds == null) { 1.424 - resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "JNDI DataSource lookup failed. See log for details."); 1.425 - return; 1.426 - } 1.427 - try (final var connection = ds.getConnection()) { 1.428 - final var dao = createDataAccessObjects(connection); 1.429 - try { 1.430 - connection.setAutoCommit(false); 1.431 - // call the handler, if available, or send an HTTP 404 error 1.432 - final var mapping = findMapping(method, req); 1.433 - if (mapping.isPresent()) { 1.434 - invokeMapping(mapping.get(), req, resp, dao); 1.435 - } else { 1.436 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 1.437 - } 1.438 - connection.commit(); 1.439 - } catch (SQLException ex) { 1.440 - LOG.warn("Database transaction failed (Code {}): {}", ex.getErrorCode(), ex.getMessage()); 1.441 - LOG.debug("Details: ", ex); 1.442 - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unhandled Transaction Error - Code: " + ex.getErrorCode()); 1.443 - connection.rollback(); 1.444 - } 1.445 - } catch (SQLException ex) { 1.446 - LOG.error("Severe Database Exception (Code {}): {}", ex.getErrorCode(), ex.getMessage()); 1.447 - LOG.debug("Details: ", ex); 1.448 - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.getErrorCode()); 1.449 - } 1.450 - } 1.451 - 1.452 - @Override 1.453 - protected final void doGet(HttpServletRequest req, HttpServletResponse resp) 1.454 - throws ServletException, IOException { 1.455 - doProcess(HttpMethod.GET, req, resp); 1.456 - } 1.457 - 1.458 - @Override 1.459 - protected final void doPost(HttpServletRequest req, HttpServletResponse resp) 1.460 - throws ServletException, IOException { 1.461 - doProcess(HttpMethod.POST, req, resp); 1.462 - } 1.463 -}
2.1 --- a/src/main/java/de/uapcore/lightpit/modules/ErrorModule.java Sat Jan 23 14:47:59 2021 +0100 2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 2.3 @@ -1,61 +0,0 @@ 2.4 -/* 2.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 2.6 - * 2.7 - * Copyright 2021 Mike Becker. All rights reserved. 2.8 - * 2.9 - * Redistribution and use in source and binary forms, with or without 2.10 - * modification, are permitted provided that the following conditions are met: 2.11 - * 2.12 - * 1. Redistributions of source code must retain the above copyright 2.13 - * notice, this list of conditions and the following disclaimer. 2.14 - * 2.15 - * 2. Redistributions in binary form must reproduce the above copyright 2.16 - * notice, this list of conditions and the following disclaimer in the 2.17 - * documentation and/or other materials provided with the distribution. 2.18 - * 2.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 2.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 2.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 2.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 2.29 - * POSSIBILITY OF SUCH DAMAGE. 2.30 - * 2.31 - */ 2.32 -package de.uapcore.lightpit.modules; 2.33 - 2.34 -import de.uapcore.lightpit.AbstractServlet; 2.35 -import de.uapcore.lightpit.HttpMethod; 2.36 -import de.uapcore.lightpit.RequestMapping; 2.37 - 2.38 -import javax.servlet.ServletException; 2.39 -import javax.servlet.annotation.WebServlet; 2.40 -import javax.servlet.http.HttpServletRequest; 2.41 -import javax.servlet.http.HttpServletResponse; 2.42 -import java.io.IOException; 2.43 -import java.util.Optional; 2.44 - 2.45 -@WebServlet( 2.46 - name = "ErrorModule", 2.47 - urlPatterns = "/error/*" 2.48 -) 2.49 -public final class ErrorModule extends AbstractServlet { 2.50 - 2.51 - public static final String REQ_ATTR_RETURN_LINK = "returnLink"; 2.52 - 2.53 - @RequestMapping(requestPath = "generic", method = HttpMethod.GET) 2.54 - public void onError(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 2.55 - Optional.ofNullable(req.getHeader("Referer")).ifPresent( 2.56 - referer -> req.setAttribute(REQ_ATTR_RETURN_LINK, referer) 2.57 - ); 2.58 - 2.59 - setStylesheet(req, "error"); 2.60 - setContentPage(req, "error"); 2.61 - 2.62 - renderSite(req, resp); 2.63 - } 2.64 -}
3.1 --- a/src/main/java/de/uapcore/lightpit/modules/LanguageModule.java Sat Jan 23 14:47:59 2021 +0100 3.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 3.3 @@ -1,113 +0,0 @@ 3.4 -/* 3.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3.6 - * 3.7 - * Copyright 2021 Mike Becker. All rights reserved. 3.8 - * 3.9 - * Redistribution and use in source and binary forms, with or without 3.10 - * modification, are permitted provided that the following conditions are met: 3.11 - * 3.12 - * 1. Redistributions of source code must retain the above copyright 3.13 - * notice, this list of conditions and the following disclaimer. 3.14 - * 3.15 - * 2. Redistributions in binary form must reproduce the above copyright 3.16 - * notice, this list of conditions and the following disclaimer in the 3.17 - * documentation and/or other materials provided with the distribution. 3.18 - * 3.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 3.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 3.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 3.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 3.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 3.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 3.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 3.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3.29 - * POSSIBILITY OF SUCH DAMAGE. 3.30 - * 3.31 - */ 3.32 -package de.uapcore.lightpit.modules; 3.33 - 3.34 -import de.uapcore.lightpit.AbstractServlet; 3.35 -import de.uapcore.lightpit.Constants; 3.36 -import de.uapcore.lightpit.HttpMethod; 3.37 -import de.uapcore.lightpit.RequestMapping; 3.38 -import de.uapcore.lightpit.viewmodel.LanguageView; 3.39 -import org.slf4j.Logger; 3.40 -import org.slf4j.LoggerFactory; 3.41 - 3.42 -import javax.servlet.ServletException; 3.43 -import javax.servlet.annotation.WebServlet; 3.44 -import javax.servlet.http.HttpServletRequest; 3.45 -import javax.servlet.http.HttpServletResponse; 3.46 -import java.io.IOException; 3.47 -import java.util.*; 3.48 - 3.49 -@WebServlet( 3.50 - name = "LanguageModule", 3.51 - urlPatterns = "/language/*" 3.52 -) 3.53 -public final class LanguageModule extends AbstractServlet { 3.54 - 3.55 - private static final Logger LOG = LoggerFactory.getLogger(LanguageModule.class); 3.56 - 3.57 - private final List<Locale> languages = new ArrayList<>(); 3.58 - 3.59 - @Override 3.60 - public void init() throws ServletException { 3.61 - super.init(); 3.62 - 3.63 - Optional<String[]> langs = availableLanguages(); 3.64 - if (langs.isPresent()) { 3.65 - for (String lang : langs.get()) { 3.66 - try { 3.67 - Locale locale = Locale.forLanguageTag(lang); 3.68 - if (locale.getLanguage().isEmpty()) { 3.69 - throw new IllformedLocaleException(); 3.70 - } 3.71 - languages.add(locale); 3.72 - } catch (IllformedLocaleException ex) { 3.73 - LOG.warn("Specified language {} in context parameter cannot be mapped to an existing locale - skipping.", lang); 3.74 - } 3.75 - } 3.76 - 3.77 - } else { 3.78 - languages.add(Locale.ENGLISH); 3.79 - LOG.warn("Context parameter 'available-languages' not found. Only english will be available."); 3.80 - } 3.81 - } 3.82 - 3.83 - @Override 3.84 - public void destroy() { 3.85 - super.destroy(); 3.86 - languages.clear(); 3.87 - } 3.88 - 3.89 - @RequestMapping(method = HttpMethod.GET) 3.90 - public void handle(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3.91 - 3.92 - final var viewModel = new LanguageView(); 3.93 - viewModel.setLanguages(languages); 3.94 - viewModel.setBrowserLanguage(req.getLocale()); 3.95 - viewModel.setCurrentLanguage((Locale)req.getSession().getAttribute(Constants.SESSION_ATTR_LANGUAGE)); 3.96 - 3.97 - setViewModel(req, viewModel); 3.98 - setStylesheet(req, "language"); 3.99 - setContentPage(req, "language"); 3.100 - 3.101 - renderSite(req, resp); 3.102 - } 3.103 - 3.104 - @RequestMapping(method = HttpMethod.POST) 3.105 - public void switchLanguage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3.106 - 3.107 - Optional<Locale> chosenLanguage = Optional.ofNullable(req.getParameter("language")) 3.108 - .map(Locale::forLanguageTag) 3.109 - .filter((l) -> !l.getLanguage().isEmpty()); 3.110 - 3.111 - chosenLanguage.ifPresent((l) -> req.getSession().setAttribute(Constants.SESSION_ATTR_LANGUAGE, l)); 3.112 - chosenLanguage.ifPresent(resp::setLocale); 3.113 - 3.114 - handle(req, resp); 3.115 - } 3.116 -}
4.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Sat Jan 23 14:47:59 2021 +0100 4.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 4.3 @@ -1,660 +0,0 @@ 4.4 -/* 4.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 4.6 - * 4.7 - * Copyright 2021 Mike Becker. All rights reserved. 4.8 - * 4.9 - * Redistribution and use in source and binary forms, with or without 4.10 - * modification, are permitted provided that the following conditions are met: 4.11 - * 4.12 - * 1. Redistributions of source code must retain the above copyright 4.13 - * notice, this list of conditions and the following disclaimer. 4.14 - * 4.15 - * 2. Redistributions in binary form must reproduce the above copyright 4.16 - * notice, this list of conditions and the following disclaimer in the 4.17 - * documentation and/or other materials provided with the distribution. 4.18 - * 4.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 4.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 4.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 4.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 4.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 4.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 4.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 4.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 4.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 4.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 4.29 - * POSSIBILITY OF SUCH DAMAGE. 4.30 - * 4.31 - */ 4.32 -package de.uapcore.lightpit.modules; 4.33 - 4.34 - 4.35 -import de.uapcore.lightpit.*; 4.36 -import de.uapcore.lightpit.dao.DataAccessObject; 4.37 -import de.uapcore.lightpit.entities.*; 4.38 -import de.uapcore.lightpit.filter.AllFilter; 4.39 -import de.uapcore.lightpit.filter.IssueFilter; 4.40 -import de.uapcore.lightpit.filter.NoneFilter; 4.41 -import de.uapcore.lightpit.filter.SpecificFilter; 4.42 -import de.uapcore.lightpit.types.IssueCategory; 4.43 -import de.uapcore.lightpit.types.IssueStatus; 4.44 -import de.uapcore.lightpit.types.VersionStatus; 4.45 -import de.uapcore.lightpit.types.WebColor; 4.46 -import de.uapcore.lightpit.viewmodel.*; 4.47 -import de.uapcore.lightpit.viewmodel.util.IssueSorter; 4.48 -import org.slf4j.Logger; 4.49 -import org.slf4j.LoggerFactory; 4.50 - 4.51 -import javax.servlet.ServletException; 4.52 -import javax.servlet.annotation.WebServlet; 4.53 -import javax.servlet.http.HttpServletRequest; 4.54 -import javax.servlet.http.HttpServletResponse; 4.55 -import java.io.IOException; 4.56 -import java.sql.Date; 4.57 -import java.sql.SQLException; 4.58 -import java.util.NoSuchElementException; 4.59 -import java.util.Optional; 4.60 -import java.util.stream.Collectors; 4.61 -import java.util.stream.Stream; 4.62 - 4.63 -@WebServlet( 4.64 - name = "ProjectsModule", 4.65 - urlPatterns = "/projects/*" 4.66 -) 4.67 -public final class ProjectsModule extends AbstractServlet { 4.68 - 4.69 - private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class); 4.70 - 4.71 - private static int parseIntOrZero(String str) { 4.72 - try { 4.73 - return Integer.parseInt(str); 4.74 - } catch (NumberFormatException ex) { 4.75 - return 0; 4.76 - } 4.77 - } 4.78 - 4.79 - private void populate(ProjectView viewModel, PathParameters pathParameters, DataAccessObject dao) { 4.80 - dao.listProjects().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add); 4.81 - 4.82 - if (pathParameters == null) 4.83 - return; 4.84 - 4.85 - // Select Project 4.86 - final var project = dao.findProjectByNode(pathParameters.get("project")); 4.87 - if (project == null) 4.88 - return; 4.89 - 4.90 - final var info = new ProjectInfo(project); 4.91 - info.setVersions(dao.listVersions(project)); 4.92 - info.setComponents(dao.listComponents(project)); 4.93 - info.setIssueSummary(dao.collectIssueSummary(project)); 4.94 - viewModel.setProjectInfo(info); 4.95 - 4.96 - // Select Version 4.97 - final var versionNode = pathParameters.get("version"); 4.98 - if (versionNode != null) { 4.99 - if ("no-version".equals(versionNode)) { 4.100 - viewModel.setVersionFilter(ProjectView.NO_VERSION); 4.101 - } else if ("all-versions".equals(versionNode)) { 4.102 - viewModel.setVersionFilter(ProjectView.ALL_VERSIONS); 4.103 - } else { 4.104 - viewModel.setVersionFilter(dao.findVersionByNode(project, versionNode)); 4.105 - } 4.106 - } 4.107 - 4.108 - // Select Component 4.109 - final var componentNode = pathParameters.get("component"); 4.110 - if (componentNode != null) { 4.111 - if ("no-component".equals(componentNode)) { 4.112 - viewModel.setComponentFilter(ProjectView.NO_COMPONENT); 4.113 - } else if ("all-components".equals(componentNode)) { 4.114 - viewModel.setComponentFilter(ProjectView.ALL_COMPONENTS); 4.115 - } else { 4.116 - viewModel.setComponentFilter(dao.findComponentByNode(project, componentNode)); 4.117 - } 4.118 - } 4.119 - } 4.120 - 4.121 - private static String sanitizeNode(String node, String defaultValue) { 4.122 - String result = node == null || node.isBlank() ? defaultValue : node; 4.123 - result = result.replace('/', '-'); 4.124 - if (result.equals(".") || result.equals("..")) { 4.125 - return "_"+result; 4.126 - } else { 4.127 - return result; 4.128 - } 4.129 - } 4.130 - 4.131 - private void forwardView(HttpServletRequest req, HttpServletResponse resp, ProjectView viewModel, String name) throws ServletException, IOException { 4.132 - setViewModel(req, viewModel); 4.133 - setContentPage(req, name); 4.134 - setStylesheet(req, "projects"); 4.135 - setNavigationMenu(req, "project-navmenu"); 4.136 - renderSite(req, resp); 4.137 - } 4.138 - 4.139 - @RequestMapping(method = HttpMethod.GET) 4.140 - public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException { 4.141 - final var viewModel = new ProjectView(); 4.142 - populate(viewModel, null, dao); 4.143 - 4.144 - for (var info : viewModel.getProjectList()) { 4.145 - info.setVersions(dao.listVersions(info.getProject())); 4.146 - info.setIssueSummary(dao.collectIssueSummary(info.getProject())); 4.147 - } 4.148 - 4.149 - forwardView(req, resp, viewModel, "projects"); 4.150 - } 4.151 - 4.152 - private void configureProjectEditor(ProjectEditView viewModel, Project project, DataAccessObject dao) { 4.153 - viewModel.setProject(project); 4.154 - viewModel.setUsers(dao.listUsers()); 4.155 - } 4.156 - 4.157 - @RequestMapping(requestPath = "$project/edit", method = HttpMethod.GET) 4.158 - public void edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws IOException, SQLException, ServletException { 4.159 - final var viewModel = new ProjectEditView(); 4.160 - populate(viewModel, pathParams, dao); 4.161 - 4.162 - if (!viewModel.isProjectInfoPresent()) { 4.163 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.164 - return; 4.165 - } 4.166 - 4.167 - configureProjectEditor(viewModel, viewModel.getProjectInfo().getProject(), dao); 4.168 - forwardView(req, resp, viewModel, "project-form"); 4.169 - } 4.170 - 4.171 - @RequestMapping(requestPath = "create", method = HttpMethod.GET) 4.172 - public void create(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { 4.173 - final var viewModel = new ProjectEditView(); 4.174 - populate(viewModel, null, dao); 4.175 - configureProjectEditor(viewModel, new Project(-1), dao); 4.176 - forwardView(req, resp, viewModel, "project-form"); 4.177 - } 4.178 - 4.179 - @RequestMapping(requestPath = "commit", method = HttpMethod.POST) 4.180 - public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { 4.181 - 4.182 - try { 4.183 - final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); 4.184 - project.setName(getParameter(req, String.class, "name").orElseThrow()); 4.185 - 4.186 - final var node = getParameter(req, String.class, "node").orElse(null); 4.187 - project.setNode(sanitizeNode(node, project.getName())); 4.188 - getParameter(req, Integer.class, "ordinal").ifPresent(project::setOrdinal); 4.189 - 4.190 - getParameter(req, String.class, "description").ifPresent(project::setDescription); 4.191 - getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl); 4.192 - getParameter(req, Integer.class, "owner").map( 4.193 - ownerId -> ownerId >= 0 ? new User(ownerId) : null 4.194 - ).ifPresent(project::setOwner); 4.195 - 4.196 - if (project.getId() > 0) { 4.197 - dao.updateProject(project); 4.198 - } else { 4.199 - dao.insertProject(project); 4.200 - } 4.201 - 4.202 - setRedirectLocation(req, "./projects/"); 4.203 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 4.204 - LOG.debug("Successfully updated project {}", project.getName()); 4.205 - 4.206 - renderSite(req, resp); 4.207 - } catch (NoSuchElementException | IllegalArgumentException ex) { 4.208 - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 4.209 - // TODO: implement - fix issue #21 4.210 - } 4.211 - } 4.212 - 4.213 - @RequestMapping(requestPath = "$project/$component/$version/issues/", method = HttpMethod.GET) 4.214 - public void issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws SQLException, IOException, ServletException { 4.215 - final var viewModel = new ProjectDetailsView(); 4.216 - populate(viewModel, pathParams, dao); 4.217 - 4.218 - if (!viewModel.isEveryFilterValid()) { 4.219 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.220 - return; 4.221 - } 4.222 - 4.223 - final var project = viewModel.getProjectInfo().getProject(); 4.224 - final var version = viewModel.getVersionFilter(); 4.225 - final var component = viewModel.getComponentFilter(); 4.226 - 4.227 - // TODO: use new IssueFilter class for the ViewModel 4.228 - 4.229 - final var projectFilter = new SpecificFilter<>(project); 4.230 - final IssueFilter filter; 4.231 - if (version.equals(ProjectView.NO_VERSION)) { 4.232 - if (component.equals(ProjectView.ALL_COMPONENTS)) { 4.233 - filter = new IssueFilter(projectFilter, 4.234 - new NoneFilter<>(), 4.235 - new AllFilter<>() 4.236 - ); 4.237 - } else if (component.equals(ProjectView.NO_COMPONENT)) { 4.238 - filter = new IssueFilter(projectFilter, 4.239 - new NoneFilter<>(), 4.240 - new NoneFilter<>() 4.241 - ); 4.242 - } else { 4.243 - filter = new IssueFilter(projectFilter, 4.244 - new NoneFilter<>(), 4.245 - new SpecificFilter<>(component) 4.246 - ); 4.247 - } 4.248 - } else if (version.equals(ProjectView.ALL_VERSIONS)) { 4.249 - if (component.equals(ProjectView.ALL_COMPONENTS)) { 4.250 - filter = new IssueFilter(projectFilter, 4.251 - new AllFilter<>(), 4.252 - new AllFilter<>() 4.253 - ); 4.254 - } else if (component.equals(ProjectView.NO_COMPONENT)) { 4.255 - filter = new IssueFilter(projectFilter, 4.256 - new AllFilter<>(), 4.257 - new NoneFilter<>() 4.258 - ); 4.259 - } else { 4.260 - filter = new IssueFilter(projectFilter, 4.261 - new AllFilter<>(), 4.262 - new SpecificFilter<>(component) 4.263 - ); 4.264 - } 4.265 - } else { 4.266 - if (component.equals(ProjectView.ALL_COMPONENTS)) { 4.267 - filter = new IssueFilter(projectFilter, 4.268 - new SpecificFilter<>(version), 4.269 - new AllFilter<>() 4.270 - ); 4.271 - } else if (component.equals(ProjectView.NO_COMPONENT)) { 4.272 - filter = new IssueFilter(projectFilter, 4.273 - new SpecificFilter<>(version), 4.274 - new NoneFilter<>() 4.275 - ); 4.276 - } else { 4.277 - filter = new IssueFilter(projectFilter, 4.278 - new SpecificFilter<>(version), 4.279 - new SpecificFilter<>(component) 4.280 - ); 4.281 - } 4.282 - } 4.283 - 4.284 - final var issues = dao.listIssues(filter); 4.285 - issues.sort(new IssueSorter( 4.286 - new IssueSorter.Criteria(IssueSorter.Field.DONE, true), 4.287 - new IssueSorter.Criteria(IssueSorter.Field.ETA, true), 4.288 - new IssueSorter.Criteria(IssueSorter.Field.UPDATED, false) 4.289 - )); 4.290 - 4.291 - 4.292 - viewModel.getProjectDetails().updateDetails(issues); 4.293 - if (version.getId() > 0) 4.294 - viewModel.getProjectDetails().updateVersionInfo(version); 4.295 - 4.296 - forwardView(req, resp, viewModel, "project-details"); 4.297 - } 4.298 - 4.299 - @RequestMapping(requestPath = "$project/versions/", method = HttpMethod.GET) 4.300 - public void versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { 4.301 - final var viewModel = new VersionsView(); 4.302 - populate(viewModel, pathParameters, dao); 4.303 - 4.304 - final var projectInfo = viewModel.getProjectInfo(); 4.305 - if (projectInfo == null) { 4.306 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.307 - return; 4.308 - } 4.309 - 4.310 - final var issues = dao.listIssues( 4.311 - new IssueFilter( 4.312 - new SpecificFilter<>(projectInfo.getProject()), 4.313 - new AllFilter<>(), 4.314 - new AllFilter<>() 4.315 - ) 4.316 - ); 4.317 - viewModel.update(projectInfo.getVersions(), issues); 4.318 - 4.319 - forwardView(req, resp, viewModel, "versions"); 4.320 - } 4.321 - 4.322 - @RequestMapping(requestPath = "$project/versions/$version/edit", method = HttpMethod.GET) 4.323 - public void editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.324 - final var viewModel = new VersionEditView(); 4.325 - populate(viewModel, pathParameters, dao); 4.326 - 4.327 - if (viewModel.getProjectInfo() == null || viewModel.getVersionFilter() == null) { 4.328 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.329 - return; 4.330 - } 4.331 - 4.332 - viewModel.setVersion(viewModel.getVersionFilter()); 4.333 - 4.334 - forwardView(req, resp, viewModel, "version-form"); 4.335 - } 4.336 - 4.337 - @RequestMapping(requestPath = "$project/create-version", method = HttpMethod.GET) 4.338 - public void createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.339 - final var viewModel = new VersionEditView(); 4.340 - populate(viewModel, pathParameters, dao); 4.341 - 4.342 - if (viewModel.getProjectInfo() == null) { 4.343 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.344 - return; 4.345 - } 4.346 - 4.347 - viewModel.setVersion(new Version(-1, viewModel.getProjectInfo().getProject().getId())); 4.348 - 4.349 - forwardView(req, resp, viewModel, "version-form"); 4.350 - } 4.351 - 4.352 - @RequestMapping(requestPath = "commit-version", method = HttpMethod.POST) 4.353 - public void commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { 4.354 - 4.355 - try { 4.356 - final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); 4.357 - if (project == null) { 4.358 - // TODO: improve error handling, because not found is not correct for this POST request 4.359 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.360 - return; 4.361 - } 4.362 - final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), project.getId()); 4.363 - version.setName(getParameter(req, String.class, "name").orElseThrow()); 4.364 - 4.365 - final var node = getParameter(req, String.class, "node").orElse(null); 4.366 - version.setNode(sanitizeNode(node, version.getName())); 4.367 - 4.368 - getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal); 4.369 - version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); 4.370 - 4.371 - if (version.getId() > 0) { 4.372 - dao.updateVersion(version); 4.373 - } else { 4.374 - dao.insertVersion(version); 4.375 - } 4.376 - 4.377 - setRedirectLocation(req, "./projects/" + project.getNode() + "/versions/"); 4.378 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 4.379 - 4.380 - renderSite(req, resp); 4.381 - } catch (NoSuchElementException | IllegalArgumentException ex) { 4.382 - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 4.383 - // TODO: implement - fix issue #21 4.384 - } 4.385 - } 4.386 - 4.387 - @RequestMapping(requestPath = "$project/components/", method = HttpMethod.GET) 4.388 - public void components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { 4.389 - final var viewModel = new ComponentsView(); 4.390 - populate(viewModel, pathParameters, dao); 4.391 - 4.392 - final var projectInfo = viewModel.getProjectInfo(); 4.393 - if (projectInfo == null) { 4.394 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.395 - return; 4.396 - } 4.397 - 4.398 - final var issues = dao.listIssues( 4.399 - new IssueFilter( 4.400 - new SpecificFilter<>(projectInfo.getProject()), 4.401 - new AllFilter<>(), 4.402 - new AllFilter<>() 4.403 - ) 4.404 - ); 4.405 - viewModel.update(projectInfo.getComponents(), issues); 4.406 - 4.407 - forwardView(req, resp, viewModel, "components"); 4.408 - } 4.409 - 4.410 - @RequestMapping(requestPath = "$project/components/$component/edit", method = HttpMethod.GET) 4.411 - public void editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.412 - final var viewModel = new ComponentEditView(); 4.413 - populate(viewModel, pathParameters, dao); 4.414 - 4.415 - if (viewModel.getProjectInfo() == null || viewModel.getComponentFilter() == null) { 4.416 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.417 - return; 4.418 - } 4.419 - 4.420 - viewModel.setComponent(viewModel.getComponentFilter()); 4.421 - viewModel.setUsers(dao.listUsers()); 4.422 - 4.423 - forwardView(req, resp, viewModel, "component-form"); 4.424 - } 4.425 - 4.426 - @RequestMapping(requestPath = "$project/create-component", method = HttpMethod.GET) 4.427 - public void createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.428 - final var viewModel = new ComponentEditView(); 4.429 - populate(viewModel, pathParameters, dao); 4.430 - 4.431 - if (viewModel.getProjectInfo() == null) { 4.432 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.433 - return; 4.434 - } 4.435 - 4.436 - viewModel.setComponent(new Component(-1, viewModel.getProjectInfo().getProject().getId())); 4.437 - viewModel.setUsers(dao.listUsers()); 4.438 - 4.439 - forwardView(req, resp, viewModel, "component-form"); 4.440 - } 4.441 - 4.442 - @RequestMapping(requestPath = "commit-component", method = HttpMethod.POST) 4.443 - public void commitComponent(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { 4.444 - 4.445 - try { 4.446 - final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); 4.447 - if (project == null) { 4.448 - // TODO: improve error handling, because not found is not correct for this POST request 4.449 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.450 - return; 4.451 - } 4.452 - final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow(), project.getId()); 4.453 - component.setName(getParameter(req, String.class, "name").orElseThrow()); 4.454 - 4.455 - final var node = getParameter(req, String.class, "node").orElse(null); 4.456 - component.setNode(sanitizeNode(node, component.getName())); 4.457 - 4.458 - component.setColor(getParameter(req, WebColor.class, "color").orElseThrow()); 4.459 - getParameter(req, Integer.class, "ordinal").ifPresent(component::setOrdinal); 4.460 - getParameter(req, Integer.class, "lead").map( 4.461 - userid -> userid >= 0 ? new User(userid) : null 4.462 - ).ifPresent(component::setLead); 4.463 - getParameter(req, String.class, "description").ifPresent(component::setDescription); 4.464 - 4.465 - if (component.getId() > 0) { 4.466 - dao.updateComponent(component); 4.467 - } else { 4.468 - dao.insertComponent(component); 4.469 - } 4.470 - 4.471 - setRedirectLocation(req, "./projects/" + project.getNode() + "/components/"); 4.472 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 4.473 - 4.474 - renderSite(req, resp); 4.475 - } catch (NoSuchElementException | IllegalArgumentException ex) { 4.476 - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 4.477 - // TODO: implement - fix issue #21 4.478 - } 4.479 - } 4.480 - 4.481 - private void configureIssueEditor(IssueEditView viewModel, Issue issue, DataAccessObject dao) { 4.482 - final var project = viewModel.getProjectInfo().getProject(); 4.483 - issue.setProject(project); // automatically set current project for new issues 4.484 - viewModel.setIssue(issue); 4.485 - viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions()); 4.486 - viewModel.setUsers(dao.listUsers()); 4.487 - viewModel.setComponents(dao.listComponents(project)); 4.488 - } 4.489 - 4.490 - @RequestMapping(requestPath = "$project/issues/$issue/view", method = HttpMethod.GET) 4.491 - public void viewIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.492 - final var viewModel = new IssueDetailView(); 4.493 - populate(viewModel, pathParameters, dao); 4.494 - 4.495 - final var projectInfo = viewModel.getProjectInfo(); 4.496 - if (projectInfo == null) { 4.497 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.498 - return; 4.499 - } 4.500 - 4.501 - final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue"))); 4.502 - if (issue == null) { 4.503 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.504 - return; 4.505 - } 4.506 - 4.507 - viewModel.setIssue(issue); 4.508 - viewModel.setComments(dao.listComments(issue)); 4.509 - 4.510 - viewModel.processMarkdown(); 4.511 - 4.512 - forwardView(req, resp, viewModel, "issue-view"); 4.513 - } 4.514 - 4.515 - // TODO: why should the issue editor be child of $project? 4.516 - @RequestMapping(requestPath = "$project/issues/$issue/edit", method = HttpMethod.GET) 4.517 - public void editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { 4.518 - final var viewModel = new IssueEditView(); 4.519 - populate(viewModel, pathParameters, dao); 4.520 - 4.521 - final var projectInfo = viewModel.getProjectInfo(); 4.522 - if (projectInfo == null) { 4.523 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.524 - return; 4.525 - } 4.526 - 4.527 - final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue"))); 4.528 - if (issue == null) { 4.529 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.530 - return; 4.531 - } 4.532 - 4.533 - configureIssueEditor(viewModel, issue, dao); 4.534 - 4.535 - forwardView(req, resp, viewModel, "issue-form"); 4.536 - } 4.537 - 4.538 - @RequestMapping(requestPath = "$project/create-issue", method = HttpMethod.GET) 4.539 - public void createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { 4.540 - final var viewModel = new IssueEditView(); 4.541 - populate(viewModel, pathParameters, dao); 4.542 - 4.543 - final var projectInfo = viewModel.getProjectInfo(); 4.544 - if (projectInfo == null) { 4.545 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.546 - return; 4.547 - } 4.548 - 4.549 - setAttributeFromParameter(req, "more"); 4.550 - setAttributeFromParameter(req, "cid"); 4.551 - setAttributeFromParameter(req, "vid"); 4.552 - 4.553 - final var issue = new Issue(-1, projectInfo.getProject(), null); 4.554 - issue.setProject(projectInfo.getProject()); 4.555 - configureIssueEditor(viewModel, issue, dao); 4.556 - 4.557 - forwardView(req, resp, viewModel, "issue-form"); 4.558 - } 4.559 - 4.560 - @RequestMapping(requestPath = "commit-issue", method = HttpMethod.POST) 4.561 - public void commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { 4.562 - try { 4.563 - final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); 4.564 - if (project == null) { 4.565 - // TODO: improve error handling, because not found is not correct for this POST request 4.566 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.567 - return; 4.568 - } 4.569 - final var componentId = getParameter(req, Integer.class, "component"); 4.570 - final Component component; 4.571 - if (componentId.isPresent()) { 4.572 - component = dao.findComponent(componentId.get()); 4.573 - } else { 4.574 - component = null; 4.575 - } 4.576 - final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), project, component); 4.577 - getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory); 4.578 - getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus); 4.579 - issue.setSubject(getParameter(req, String.class, "subject").orElseThrow()); 4.580 - getParameter(req, Integer.class, "assignee").map(userid -> { 4.581 - if (userid >= 0) { 4.582 - return new User(userid); 4.583 - } else if (userid == -2) { 4.584 - return Optional.ofNullable(component).map(Component::getLead).orElse(null); 4.585 - } else { 4.586 - return null; 4.587 - } 4.588 - } 4.589 - ).ifPresent(issue::setAssignee); 4.590 - getParameter(req, String.class, "description").ifPresent(issue::setDescription); 4.591 - getParameter(req, Date.class, "eta").ifPresent(issue::setEta); 4.592 - 4.593 - getParameter(req, Integer[].class, "affected") 4.594 - .map(Stream::of) 4.595 - .map(stream -> 4.596 - stream.map(id -> new Version(id, project.getId())) 4.597 - .collect(Collectors.toList()) 4.598 - ).ifPresent(issue::setAffectedVersions); 4.599 - getParameter(req, Integer[].class, "resolved") 4.600 - .map(Stream::of) 4.601 - .map(stream -> 4.602 - stream.map(id -> new Version(id, project.getId())) 4.603 - .collect(Collectors.toList()) 4.604 - ).ifPresent(issue::setResolvedVersions); 4.605 - 4.606 - if (issue.getId() > 0) { 4.607 - dao.updateIssue(issue); 4.608 - } else { 4.609 - dao.insertIssue(issue); 4.610 - } 4.611 - 4.612 - if (getParameter(req, Boolean.class, "create-another").orElse(false)) { 4.613 - // TODO: fix #38 - automatically select component (and version) 4.614 - setRedirectLocation(req, "./projects/" + issue.getProject().getNode() + "/create-issue?more=true"); 4.615 - } else{ 4.616 - setRedirectLocation(req, "./projects/" + issue.getProject().getNode() + "/issues/" + issue.getId() + "/view"); 4.617 - } 4.618 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 4.619 - 4.620 - renderSite(req, resp); 4.621 - } catch (NoSuchElementException | IllegalArgumentException ex) { 4.622 - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 4.623 - // TODO: implement - fix issue #21 4.624 - } 4.625 - } 4.626 - 4.627 - @RequestMapping(requestPath = "commit-issue-comment", method = HttpMethod.POST) 4.628 - public void commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { 4.629 - final var issueIdParam = getParameter(req, Integer.class, "issueid"); 4.630 - if (issueIdParam.isEmpty()) { 4.631 - resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Detected manipulated form."); 4.632 - return; 4.633 - } 4.634 - final var issue = dao.findIssue(issueIdParam.get()); 4.635 - if (issue == null) { 4.636 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 4.637 - return; 4.638 - } 4.639 - try { 4.640 - final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue.getId()); 4.641 - issueComment.setComment(getParameter(req, String.class, "comment").orElse("")); 4.642 - 4.643 - if (issueComment.getComment().isBlank()) { 4.644 - throw new IllegalArgumentException("comment.null"); 4.645 - } 4.646 - 4.647 - LOG.debug("User {} is commenting on issue #{}", req.getRemoteUser(), issue.getId()); 4.648 - if (req.getRemoteUser() != null) { 4.649 - Optional.ofNullable(dao.findUserByName(req.getRemoteUser())).ifPresent(issueComment::setAuthor); 4.650 - } 4.651 - 4.652 - dao.insertComment(issueComment); 4.653 - 4.654 - setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view"); 4.655 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 4.656 - 4.657 - renderSite(req, resp); 4.658 - } catch (NoSuchElementException | IllegalArgumentException ex) { 4.659 - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 4.660 - // TODO: implement - fix issue #21 4.661 - } 4.662 - } 4.663 -}
5.1 --- a/src/main/java/de/uapcore/lightpit/modules/UsersModule.java Sat Jan 23 14:47:59 2021 +0100 5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 5.3 @@ -1,113 +0,0 @@ 5.4 -/* 5.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 5.6 - * 5.7 - * Copyright 2021 Mike Becker. All rights reserved. 5.8 - * 5.9 - * Redistribution and use in source and binary forms, with or without 5.10 - * modification, are permitted provided that the following conditions are met: 5.11 - * 5.12 - * 1. Redistributions of source code must retain the above copyright 5.13 - * notice, this list of conditions and the following disclaimer. 5.14 - * 5.15 - * 2. Redistributions in binary form must reproduce the above copyright 5.16 - * notice, this list of conditions and the following disclaimer in the 5.17 - * documentation and/or other materials provided with the distribution. 5.18 - * 5.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 5.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 5.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 5.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 5.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 5.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 5.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 5.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 5.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 5.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 5.29 - * POSSIBILITY OF SUCH DAMAGE. 5.30 - * 5.31 - */ 5.32 -package de.uapcore.lightpit.modules; 5.33 - 5.34 -import de.uapcore.lightpit.AbstractServlet; 5.35 -import de.uapcore.lightpit.Constants; 5.36 -import de.uapcore.lightpit.HttpMethod; 5.37 -import de.uapcore.lightpit.RequestMapping; 5.38 -import de.uapcore.lightpit.dao.DataAccessObject; 5.39 -import de.uapcore.lightpit.entities.User; 5.40 -import de.uapcore.lightpit.viewmodel.UsersEditView; 5.41 -import de.uapcore.lightpit.viewmodel.UsersView; 5.42 -import org.slf4j.Logger; 5.43 -import org.slf4j.LoggerFactory; 5.44 - 5.45 -import javax.servlet.ServletException; 5.46 -import javax.servlet.annotation.WebServlet; 5.47 -import javax.servlet.http.HttpServletRequest; 5.48 -import javax.servlet.http.HttpServletResponse; 5.49 -import java.io.IOException; 5.50 -import java.sql.SQLException; 5.51 -import java.util.NoSuchElementException; 5.52 - 5.53 -@WebServlet( 5.54 - name = "UsersModule", 5.55 - urlPatterns = "/teams/*" 5.56 -) 5.57 -public final class UsersModule extends AbstractServlet { 5.58 - 5.59 - private static final Logger LOG = LoggerFactory.getLogger(UsersModule.class); 5.60 - 5.61 - @RequestMapping(method = HttpMethod.GET) 5.62 - public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { 5.63 - final var viewModel = new UsersView(); 5.64 - viewModel.setUsers(dao.listUsers()); 5.65 - setViewModel(req, viewModel); 5.66 - setContentPage(req, "users"); 5.67 - 5.68 - renderSite(req, resp); 5.69 - } 5.70 - 5.71 - @RequestMapping(requestPath = "edit", method = HttpMethod.GET) 5.72 - public void edit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { 5.73 - 5.74 - final var viewModel = new UsersEditView(); 5.75 - viewModel.setUser(findByParameter(req, Integer.class, "id", dao::findUser).orElse(new User(-1))); 5.76 - 5.77 - setViewModel(req, viewModel); 5.78 - setContentPage(req, "user-form"); 5.79 - 5.80 - renderSite(req, resp); 5.81 - } 5.82 - 5.83 - @RequestMapping(requestPath = "commit", method = HttpMethod.POST) 5.84 - public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException { 5.85 - 5.86 - User user = new User(-1); 5.87 - try { 5.88 - user = new User(getParameter(req, Integer.class, "userid").orElseThrow()); 5.89 - user.setUsername(getParameter(req, String.class, "username").orElseThrow()); 5.90 - getParameter(req, String.class, "givenname").ifPresent(user::setGivenname); 5.91 - getParameter(req, String.class, "lastname").ifPresent(user::setLastname); 5.92 - getParameter(req, String.class, "mail").ifPresent(user::setMail); 5.93 - 5.94 - if (user.getId() > 0) { 5.95 - dao.updateUser(user); 5.96 - } else { 5.97 - dao.insertUser(user); 5.98 - } 5.99 - 5.100 - setRedirectLocation(req, "./teams/"); 5.101 - setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 5.102 - 5.103 - LOG.debug("Successfully updated user {}", user.getUsername()); 5.104 - } catch (NoSuchElementException | IllegalArgumentException ex) { 5.105 - final var viewModel = new UsersEditView(); 5.106 - viewModel.setUser(user); 5.107 - // TODO: viewModel.setErrorText() 5.108 - setViewModel(req, viewModel); 5.109 - setContentPage(req, "user-form"); 5.110 - LOG.warn("Form validation failure: {}", ex.getMessage()); 5.111 - LOG.debug("Details:", ex); 5.112 - } 5.113 - 5.114 - renderSite(req, resp); 5.115 - } 5.116 -}
6.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ComponentEditView.java Sat Jan 23 14:47:59 2021 +0100 6.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 6.3 @@ -1,40 +0,0 @@ 6.4 -package de.uapcore.lightpit.viewmodel; 6.5 - 6.6 -import de.uapcore.lightpit.entities.Component; 6.7 -import de.uapcore.lightpit.entities.User; 6.8 - 6.9 -import java.util.List; 6.10 - 6.11 -public class ComponentEditView extends ProjectView { 6.12 - private Component component; 6.13 - private List<User> users; 6.14 - private String errorText; 6.15 - 6.16 - public ComponentEditView() { 6.17 - setSelectedPage(SELECTED_PAGE_COMPONENTS); 6.18 - } 6.19 - 6.20 - public void setComponent(Component component) { 6.21 - this.component = component; 6.22 - } 6.23 - 6.24 - public Component getComponent() { 6.25 - return component; 6.26 - } 6.27 - 6.28 - public List<User> getUsers() { 6.29 - return users; 6.30 - } 6.31 - 6.32 - public void setUsers(List<User> users) { 6.33 - this.users = users; 6.34 - } 6.35 - 6.36 - public String getErrorText() { 6.37 - return errorText; 6.38 - } 6.39 - 6.40 - public void setErrorText(String errorText) { 6.41 - this.errorText = errorText; 6.42 - } 6.43 -}
7.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ComponentInfo.java Sat Jan 23 14:47:59 2021 +0100 7.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 7.3 @@ -1,42 +0,0 @@ 7.4 -package de.uapcore.lightpit.viewmodel; 7.5 - 7.6 -import de.uapcore.lightpit.entities.Component; 7.7 -import de.uapcore.lightpit.entities.Issue; 7.8 -import de.uapcore.lightpit.entities.IssueSummary; 7.9 - 7.10 -import java.util.ArrayList; 7.11 -import java.util.List; 7.12 - 7.13 -public class ComponentInfo { 7.14 - 7.15 - private final Component component; 7.16 - 7.17 - private final IssueSummary issueSummary = new IssueSummary(); 7.18 - 7.19 - private final List<Issue> issues = new ArrayList<>(); 7.20 - 7.21 - public ComponentInfo(Component component) { 7.22 - this.component = component; 7.23 - } 7.24 - 7.25 - public Component getComponent() { 7.26 - return component; 7.27 - } 7.28 - 7.29 - public IssueSummary getIssueSummary() { 7.30 - return issueSummary; 7.31 - } 7.32 - 7.33 - public List<Issue> getIssues() { 7.34 - return issues; 7.35 - } 7.36 - 7.37 - public void collectIssues(List<Issue> issues) { 7.38 - for (Issue issue : issues) { 7.39 - if (component.equals(issue.getComponent())) { 7.40 - this.issues.add(issue); 7.41 - this.issueSummary.add(issue); 7.42 - } 7.43 - } 7.44 - } 7.45 -}
8.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ComponentsView.java Sat Jan 23 14:47:59 2021 +0100 8.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 8.3 @@ -1,29 +0,0 @@ 8.4 -package de.uapcore.lightpit.viewmodel; 8.5 - 8.6 -import de.uapcore.lightpit.entities.Component; 8.7 -import de.uapcore.lightpit.entities.Issue; 8.8 - 8.9 -import java.util.ArrayList; 8.10 -import java.util.List; 8.11 - 8.12 -public class ComponentsView extends ProjectView { 8.13 - 8.14 - private List<ComponentInfo> componentInfos = new ArrayList<>(); 8.15 - 8.16 - public ComponentsView() { 8.17 - setSelectedPage(SELECTED_PAGE_COMPONENTS); 8.18 - } 8.19 - 8.20 - public void update(List<Component> components, List<Issue> issues) { 8.21 - componentInfos.clear(); 8.22 - for (var component : components) { 8.23 - final var info = new ComponentInfo(component); 8.24 - info.collectIssues(issues); 8.25 - componentInfos.add(info); 8.26 - } 8.27 - } 8.28 - 8.29 - public List<ComponentInfo> getComponentInfos() { 8.30 - return componentInfos; 8.31 - } 8.32 -}
9.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/IssueDetailView.java Sat Jan 23 14:47:59 2021 +0100 9.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 9.3 @@ -1,47 +0,0 @@ 9.4 -package de.uapcore.lightpit.viewmodel; 9.5 - 9.6 -import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension; 9.7 -import com.vladsch.flexmark.ext.tables.TablesExtension; 9.8 -import com.vladsch.flexmark.html.HtmlRenderer; 9.9 -import com.vladsch.flexmark.parser.Parser; 9.10 -import com.vladsch.flexmark.util.data.MutableDataSet; 9.11 -import de.uapcore.lightpit.entities.Issue; 9.12 -import de.uapcore.lightpit.entities.IssueComment; 9.13 - 9.14 -import java.util.Arrays; 9.15 -import java.util.List; 9.16 - 9.17 -public class IssueDetailView extends ProjectView { 9.18 - private Issue issue; 9.19 - 9.20 - private List<IssueComment> comments; 9.21 - 9.22 - public void setIssue(Issue issue) { 9.23 - this.issue = issue; 9.24 - } 9.25 - 9.26 - public Issue getIssue() { 9.27 - return issue; 9.28 - } 9.29 - 9.30 - public List<IssueComment> getComments() { 9.31 - return comments; 9.32 - } 9.33 - 9.34 - public void setComments(List<IssueComment> comments) { 9.35 - this.comments = comments; 9.36 - } 9.37 - 9.38 - public void processMarkdown() { 9.39 - final var options = new MutableDataSet() 9.40 - .set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create(), StrikethroughExtension.create())) 9.41 - .toImmutable(); 9.42 - final var parser = Parser.builder(options).build(); 9.43 - final var renderer = HtmlRenderer.builder(options).build(); 9.44 - 9.45 - issue.setDescription(renderer.render(parser.parse(issue.getDescription()))); 9.46 - for (var comment : comments) { 9.47 - comment.setComment(renderer.render(parser.parse(comment.getComment()))); 9.48 - } 9.49 - } 9.50 -}
10.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java Sat Jan 23 14:47:59 2021 +0100 10.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 10.3 @@ -1,75 +0,0 @@ 10.4 -package de.uapcore.lightpit.viewmodel; 10.5 - 10.6 -import de.uapcore.lightpit.entities.Component; 10.7 -import de.uapcore.lightpit.entities.Project; 10.8 -import de.uapcore.lightpit.entities.User; 10.9 -import de.uapcore.lightpit.entities.Version; 10.10 -import de.uapcore.lightpit.types.IssueCategory; 10.11 -import de.uapcore.lightpit.types.IssueStatus; 10.12 -import de.uapcore.lightpit.types.VersionStatus; 10.13 - 10.14 -import java.util.*; 10.15 - 10.16 -public class IssueEditView extends IssueDetailView { 10.17 - private List<Project> projects = Collections.emptyList(); 10.18 - private Set<Version> versionsUpcoming = new HashSet<>(); 10.19 - private Set<Version> versionsRecent = new HashSet<>(); 10.20 - private List<User> users; 10.21 - private List<Component> components; 10.22 - 10.23 - public List<Project> getProjects() { 10.24 - return projects; 10.25 - } 10.26 - 10.27 - public void setProjects(List<Project> projects) { 10.28 - this.projects = projects; 10.29 - } 10.30 - 10.31 - public Collection<Version> getVersionsUpcoming() { 10.32 - return versionsUpcoming; 10.33 - } 10.34 - 10.35 - public Collection<Version> getVersionsRecent() { 10.36 - return versionsRecent; 10.37 - } 10.38 - 10.39 - public void configureVersionSelectors(List<Version> versions) { 10.40 - versionsRecent.clear(); 10.41 - versionsUpcoming.clear(); 10.42 - // keep the current selection, if any 10.43 - versionsRecent.addAll(getIssue().getAffectedVersions()); 10.44 - versionsUpcoming.addAll(getIssue().getResolvedVersions()); 10.45 - for (var v : versions) { 10.46 - if (v.getStatus().isReleased()) { 10.47 - if (!v.getStatus().equals(VersionStatus.Deprecated)) 10.48 - versionsRecent.add(v); 10.49 - } else { 10.50 - versionsUpcoming.add(v); 10.51 - } 10.52 - } 10.53 - } 10.54 - 10.55 - public List<User> getUsers() { 10.56 - return users; 10.57 - } 10.58 - 10.59 - public void setUsers(List<User> users) { 10.60 - this.users = users; 10.61 - } 10.62 - 10.63 - public List<Component> getComponents() { 10.64 - return components; 10.65 - } 10.66 - 10.67 - public void setComponents(List<Component> components) { 10.68 - this.components = components; 10.69 - } 10.70 - 10.71 - public IssueStatus[] getIssueStatus() { 10.72 - return IssueStatus.values(); 10.73 - } 10.74 - 10.75 - public IssueCategory[] getIssueCategory() { 10.76 - return IssueCategory.values(); 10.77 - } 10.78 -}
11.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/IssuesView.java Sat Jan 23 14:47:59 2021 +0100 11.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 11.3 @@ -1,37 +0,0 @@ 11.4 -package de.uapcore.lightpit.viewmodel; 11.5 - 11.6 -import de.uapcore.lightpit.entities.Issue; 11.7 -import de.uapcore.lightpit.entities.Project; 11.8 -import de.uapcore.lightpit.entities.Version; 11.9 - 11.10 -import java.util.List; 11.11 - 11.12 -public class IssuesView { 11.13 - private List<Issue> issues; 11.14 - private Project project; 11.15 - private Version version; 11.16 - 11.17 - public List<Issue> getIssues() { 11.18 - return issues; 11.19 - } 11.20 - 11.21 - public void setIssues(List<Issue> issues) { 11.22 - this.issues = issues; 11.23 - } 11.24 - 11.25 - public Version getVersion() { 11.26 - return version; 11.27 - } 11.28 - 11.29 - public void setVersion(Version version) { 11.30 - this.version = version; 11.31 - } 11.32 - 11.33 - public Project getProject() { 11.34 - return project; 11.35 - } 11.36 - 11.37 - public void setProject(Project project) { 11.38 - this.project = project; 11.39 - } 11.40 -}
12.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/LanguageView.java Sat Jan 23 14:47:59 2021 +0100 12.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 12.3 @@ -1,36 +0,0 @@ 12.4 -package de.uapcore.lightpit.viewmodel; 12.5 - 12.6 -import java.util.List; 12.7 -import java.util.Locale; 12.8 - 12.9 -public class LanguageView { 12.10 - 12.11 - private List<Locale> languages; 12.12 - private Locale browserLanguage; 12.13 - private Locale currentLanguage; 12.14 - 12.15 - 12.16 - public List<Locale> getLanguages() { 12.17 - return languages; 12.18 - } 12.19 - 12.20 - public void setLanguages(List<Locale> languages) { 12.21 - this.languages = languages; 12.22 - } 12.23 - 12.24 - public Locale getBrowserLanguage() { 12.25 - return browserLanguage; 12.26 - } 12.27 - 12.28 - public void setBrowserLanguage(Locale browserLanguage) { 12.29 - this.browserLanguage = browserLanguage; 12.30 - } 12.31 - 12.32 - public Locale getCurrentLanguage() { 12.33 - return currentLanguage; 12.34 - } 12.35 - 12.36 - public void setCurrentLanguage(Locale currentLanguage) { 12.37 - this.currentLanguage = currentLanguage; 12.38 - } 12.39 -}
13.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectDetails.java Sat Jan 23 14:47:59 2021 +0100 13.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 13.3 @@ -1,38 +0,0 @@ 13.4 -package de.uapcore.lightpit.viewmodel; 13.5 - 13.6 -import de.uapcore.lightpit.entities.Issue; 13.7 -import de.uapcore.lightpit.entities.IssueSummary; 13.8 -import de.uapcore.lightpit.entities.Version; 13.9 - 13.10 -import java.util.List; 13.11 - 13.12 -public class ProjectDetails { 13.13 - 13.14 - private VersionInfo versionInfo = null; 13.15 - 13.16 - private List<Issue> issues; 13.17 - private IssueSummary issueSummary; 13.18 - 13.19 - public void updateDetails(List<Issue> issues) { 13.20 - this.issues = issues; 13.21 - issueSummary = new IssueSummary(); 13.22 - issues.forEach(issueSummary::add); 13.23 - } 13.24 - 13.25 - public void updateVersionInfo(Version version) { 13.26 - versionInfo = new VersionInfo(version); 13.27 - versionInfo.collectIssues(issues); 13.28 - } 13.29 - 13.30 - public List<Issue> getIssues() { 13.31 - return issues; 13.32 - } 13.33 - 13.34 - public IssueSummary getIssueSummary() { 13.35 - return issueSummary; 13.36 - } 13.37 - 13.38 - public VersionInfo getVersionInfo() { 13.39 - return versionInfo; 13.40 - } 13.41 -}
14.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectDetailsView.java Sat Jan 23 14:47:59 2021 +0100 14.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 14.3 @@ -1,10 +0,0 @@ 14.4 -package de.uapcore.lightpit.viewmodel; 14.5 - 14.6 -public class ProjectDetailsView extends ProjectView { 14.7 - 14.8 - private final ProjectDetails projectDetails = new ProjectDetails(); 14.9 - 14.10 - public ProjectDetails getProjectDetails() { 14.11 - return projectDetails; 14.12 - } 14.13 -}
15.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectEditView.java Sat Jan 23 14:47:59 2021 +0100 15.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 15.3 @@ -1,37 +0,0 @@ 15.4 -package de.uapcore.lightpit.viewmodel; 15.5 - 15.6 -import de.uapcore.lightpit.entities.Project; 15.7 -import de.uapcore.lightpit.entities.User; 15.8 - 15.9 -import java.util.List; 15.10 - 15.11 -public class ProjectEditView extends ProjectView { 15.12 - 15.13 - private Project project; 15.14 - private List<User> users; 15.15 - private String errorText; 15.16 - 15.17 - public Project getProject() { 15.18 - return project; 15.19 - } 15.20 - 15.21 - public void setProject(Project project) { 15.22 - this.project = project; 15.23 - } 15.24 - 15.25 - public List<User> getUsers() { 15.26 - return users; 15.27 - } 15.28 - 15.29 - public void setUsers(List<User> users) { 15.30 - this.users = users; 15.31 - } 15.32 - 15.33 - public String getErrorText() { 15.34 - return errorText; 15.35 - } 15.36 - 15.37 - public void setErrorText(String errorText) { 15.38 - this.errorText = errorText; 15.39 - } 15.40 -}
16.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectInfo.java Sat Jan 23 14:47:59 2021 +0100 16.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 16.3 @@ -1,69 +0,0 @@ 16.4 -package de.uapcore.lightpit.viewmodel; 16.5 - 16.6 -import de.uapcore.lightpit.entities.Component; 16.7 -import de.uapcore.lightpit.entities.IssueSummary; 16.8 -import de.uapcore.lightpit.entities.Project; 16.9 -import de.uapcore.lightpit.entities.Version; 16.10 - 16.11 -import java.util.Collections; 16.12 -import java.util.List; 16.13 - 16.14 -public class ProjectInfo { 16.15 - 16.16 - private final Project project; 16.17 - private List<Version> versions = Collections.emptyList(); 16.18 - private List<Component> components = Collections.emptyList(); 16.19 - private IssueSummary issueSummary = new IssueSummary(); 16.20 - 16.21 - public ProjectInfo(Project project) { 16.22 - this.project = project; 16.23 - } 16.24 - 16.25 - public Project getProject() { 16.26 - return project; 16.27 - } 16.28 - 16.29 - public List<Version> getVersions() { 16.30 - return versions; 16.31 - } 16.32 - 16.33 - public void setVersions(List<Version> versions) { 16.34 - this.versions = versions; 16.35 - } 16.36 - 16.37 - public List<Component> getComponents() { 16.38 - return components; 16.39 - } 16.40 - 16.41 - public void setComponents(List<Component> components) { 16.42 - this.components = components; 16.43 - } 16.44 - 16.45 - public Version getLatestVersion() { 16.46 - // expects versions to be sorted by status descending 16.47 - for (var v : versions) { 16.48 - if (v.getStatus().isReleased()) 16.49 - return v; 16.50 - } 16.51 - return null; 16.52 - } 16.53 - 16.54 - public Version getNextVersion() { 16.55 - // expects versions to be sorted by status descending 16.56 - Version next = null; 16.57 - for (var v : versions) { 16.58 - if (v.getStatus().isReleased()) 16.59 - break; 16.60 - next = v; 16.61 - } 16.62 - return next; 16.63 - } 16.64 - 16.65 - public IssueSummary getIssueSummary() { 16.66 - return issueSummary; 16.67 - } 16.68 - 16.69 - public void setIssueSummary(IssueSummary issueSummary) { 16.70 - this.issueSummary = issueSummary; 16.71 - } 16.72 -}
17.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java Sat Jan 23 14:47:59 2021 +0100 17.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 17.3 @@ -1,79 +0,0 @@ 17.4 -package de.uapcore.lightpit.viewmodel; 17.5 - 17.6 -import de.uapcore.lightpit.entities.Component; 17.7 -import de.uapcore.lightpit.entities.Version; 17.8 - 17.9 -import java.util.ArrayList; 17.10 -import java.util.List; 17.11 - 17.12 -public class ProjectView { 17.13 - 17.14 - public static final int SELECTED_PAGE_ISSUES = 0; 17.15 - public static final int SELECTED_PAGE_VERSIONS = 1; 17.16 - public static final int SELECTED_PAGE_COMPONENTS = 2; 17.17 - 17.18 - // TODO: use new Filter class 17.19 - 17.20 - public static final Version ALL_VERSIONS = new Version(0,0); 17.21 - public static final Version NO_VERSION = new Version(-1,0); 17.22 - public static final Component ALL_COMPONENTS = new Component(0,0); 17.23 - public static final Component NO_COMPONENT = new Component(-1,0); 17.24 - 17.25 - static { 17.26 - ALL_VERSIONS.setNode("all-versions"); 17.27 - NO_VERSION.setNode("no-version"); 17.28 - ALL_COMPONENTS.setNode("all-components"); 17.29 - NO_COMPONENT.setNode("no-component"); 17.30 - } 17.31 - 17.32 - private final List<ProjectInfo> projectList = new ArrayList<>(); 17.33 - private ProjectInfo projectInfo; 17.34 - private Version versionFilter; 17.35 - private Component componentFilter; 17.36 - 17.37 - private int selectedPage = SELECTED_PAGE_ISSUES; 17.38 - 17.39 - public List<ProjectInfo> getProjectList() { 17.40 - return projectList; 17.41 - } 17.42 - 17.43 - public ProjectInfo getProjectInfo() { 17.44 - return projectInfo; 17.45 - } 17.46 - 17.47 - public void setProjectInfo(ProjectInfo projectInfo) { 17.48 - this.projectInfo = projectInfo; 17.49 - } 17.50 - 17.51 - public int getSelectedPage() { 17.52 - return selectedPage; 17.53 - } 17.54 - 17.55 - public void setSelectedPage(int selectedPage) { 17.56 - this.selectedPage = selectedPage; 17.57 - } 17.58 - 17.59 - public Version getVersionFilter() { 17.60 - return versionFilter; 17.61 - } 17.62 - 17.63 - public void setVersionFilter(Version versionFilter) { 17.64 - this.versionFilter = versionFilter; 17.65 - } 17.66 - 17.67 - public Component getComponentFilter() { 17.68 - return componentFilter; 17.69 - } 17.70 - 17.71 - public void setComponentFilter(Component componentFilter) { 17.72 - this.componentFilter = componentFilter; 17.73 - } 17.74 - 17.75 - public boolean isProjectInfoPresent() { 17.76 - return projectInfo != null; 17.77 - } 17.78 - 17.79 - public boolean isEveryFilterValid() { 17.80 - return projectInfo != null && versionFilter != null && componentFilter != null; 17.81 - } 17.82 -}
18.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/UsersEditView.java Sat Jan 23 14:47:59 2021 +0100 18.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 18.3 @@ -1,24 +0,0 @@ 18.4 -package de.uapcore.lightpit.viewmodel; 18.5 - 18.6 -import de.uapcore.lightpit.entities.User; 18.7 - 18.8 -public class UsersEditView { 18.9 - private User user; 18.10 - private String errorText; 18.11 - 18.12 - public User getUser() { 18.13 - return user; 18.14 - } 18.15 - 18.16 - public void setUser(User user) { 18.17 - this.user = user; 18.18 - } 18.19 - 18.20 - public String getErrorText() { 18.21 - return errorText; 18.22 - } 18.23 - 18.24 - public void setErrorText(String errorText) { 18.25 - this.errorText = errorText; 18.26 - } 18.27 -}
19.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/UsersView.java Sat Jan 23 14:47:59 2021 +0100 19.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 19.3 @@ -1,17 +0,0 @@ 19.4 -package de.uapcore.lightpit.viewmodel; 19.5 - 19.6 -import de.uapcore.lightpit.entities.User; 19.7 - 19.8 -import java.util.List; 19.9 - 19.10 -public class UsersView { 19.11 - private List<User> users; 19.12 - 19.13 - public List<User> getUsers() { 19.14 - return users; 19.15 - } 19.16 - 19.17 - public void setUsers(List<User> users) { 19.18 - this.users = users; 19.19 - } 19.20 -}
20.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java Sat Jan 23 14:47:59 2021 +0100 20.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 20.3 @@ -1,33 +0,0 @@ 20.4 -package de.uapcore.lightpit.viewmodel; 20.5 - 20.6 -import de.uapcore.lightpit.entities.Version; 20.7 -import de.uapcore.lightpit.types.VersionStatus; 20.8 - 20.9 -public class VersionEditView extends ProjectView { 20.10 - private Version version; 20.11 - private String errorText; 20.12 - 20.13 - public VersionEditView() { 20.14 - setSelectedPage(SELECTED_PAGE_VERSIONS); 20.15 - } 20.16 - 20.17 - public void setVersion(Version version) { 20.18 - this.version = version; 20.19 - } 20.20 - 20.21 - public Version getVersion() { 20.22 - return version; 20.23 - } 20.24 - 20.25 - public VersionStatus[] getVersionStatus() { 20.26 - return VersionStatus.values(); 20.27 - } 20.28 - 20.29 - public String getErrorText() { 20.30 - return errorText; 20.31 - } 20.32 - 20.33 - public void setErrorText(String errorText) { 20.34 - this.errorText = errorText; 20.35 - } 20.36 -}
21.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionInfo.java Sat Jan 23 14:47:59 2021 +0100 21.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 21.3 @@ -1,64 +0,0 @@ 21.4 -package de.uapcore.lightpit.viewmodel; 21.5 - 21.6 -import de.uapcore.lightpit.entities.Issue; 21.7 -import de.uapcore.lightpit.entities.IssueSummary; 21.8 -import de.uapcore.lightpit.entities.Version; 21.9 - 21.10 -import java.util.ArrayList; 21.11 -import java.util.List; 21.12 - 21.13 -public class VersionInfo { 21.14 - 21.15 - private final Version version; 21.16 - 21.17 - private final IssueSummary reportedTotal = new IssueSummary(); 21.18 - private final IssueSummary resolvedTotal = new IssueSummary(); 21.19 - 21.20 - private final List<Issue> reported = new ArrayList<>(); 21.21 - private final List<Issue> resolved = new ArrayList<>(); 21.22 - 21.23 - public VersionInfo(Version version) { 21.24 - this.version = version; 21.25 - } 21.26 - 21.27 - public Version getVersion() { 21.28 - return version; 21.29 - } 21.30 - 21.31 - public void addReported(Issue issue) { 21.32 - reportedTotal.add(issue); 21.33 - reported.add(issue); 21.34 - } 21.35 - 21.36 - public void addResolved(Issue issue) { 21.37 - resolvedTotal.add(issue); 21.38 - resolved.add(issue); 21.39 - } 21.40 - 21.41 - public IssueSummary getReportedTotal() { 21.42 - return reportedTotal; 21.43 - } 21.44 - 21.45 - public IssueSummary getResolvedTotal() { 21.46 - return resolvedTotal; 21.47 - } 21.48 - 21.49 - public List<Issue> getReported() { 21.50 - return reported; 21.51 - } 21.52 - 21.53 - public List<Issue> getResolved() { 21.54 - return resolved; 21.55 - } 21.56 - 21.57 - public void collectIssues(List<Issue> issues) { 21.58 - for (Issue issue : issues) { 21.59 - if (issue.getAffectedVersions().contains(version)) { 21.60 - addReported(issue); 21.61 - } 21.62 - if (issue.getResolvedVersions().contains(version)) { 21.63 - addResolved(issue); 21.64 - } 21.65 - } 21.66 - } 21.67 -}
22.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java Sat Jan 23 14:47:59 2021 +0100 22.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 22.3 @@ -1,29 +0,0 @@ 22.4 -package de.uapcore.lightpit.viewmodel; 22.5 - 22.6 -import de.uapcore.lightpit.entities.Issue; 22.7 -import de.uapcore.lightpit.entities.Version; 22.8 - 22.9 -import java.util.ArrayList; 22.10 -import java.util.List; 22.11 - 22.12 -public class VersionsView extends ProjectView { 22.13 - 22.14 - private List<VersionInfo> versionInfos = new ArrayList<>(); 22.15 - 22.16 - public VersionsView() { 22.17 - setSelectedPage(SELECTED_PAGE_VERSIONS); 22.18 - } 22.19 - 22.20 - public void update(List<Version> versions, List<Issue> issues) { 22.21 - versionInfos.clear(); 22.22 - for (var version : versions) { 22.23 - final var info = new VersionInfo(version); 22.24 - info.collectIssues(issues); 22.25 - versionInfos.add(info); 22.26 - } 22.27 - } 22.28 - 22.29 - public List<VersionInfo> getVersionInfos() { 22.30 - return versionInfos; 22.31 - } 22.32 -}
23.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java Sat Jan 23 14:47:59 2021 +0100 23.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 23.3 @@ -1,83 +0,0 @@ 23.4 -package de.uapcore.lightpit.viewmodel.util; 23.5 - 23.6 -import de.uapcore.lightpit.entities.Issue; 23.7 -import de.uapcore.lightpit.types.IssueStatusPhase; 23.8 - 23.9 -import java.util.Arrays; 23.10 -import java.util.Comparator; 23.11 - 23.12 -public class IssueSorter implements Comparator<Issue> { 23.13 - 23.14 - public enum Field { 23.15 - DONE, ETA, UPDATED 23.16 - } 23.17 - 23.18 - public static class Criteria { 23.19 - private Field field; 23.20 - private boolean asc; 23.21 - 23.22 - public Criteria(Field field, boolean asc) { 23.23 - this.field = field; 23.24 - this.asc = asc; 23.25 - } 23.26 - 23.27 - @Override 23.28 - public boolean equals(Object obj) { 23.29 - if (obj == null || !obj.getClass().equals(Criteria.class)) 23.30 - return false; 23.31 - final var other = (Criteria)obj; 23.32 - return other.field.equals(field) && other.asc == asc; 23.33 - } 23.34 - } 23.35 - 23.36 - private final Criteria[] criteria; 23.37 - 23.38 - public IssueSorter(Criteria ... criteria) { 23.39 - this.criteria = criteria; 23.40 - } 23.41 - 23.42 - private int compare(Issue left, Issue right, Criteria criteria) { 23.43 - if (left.equals(right)) 23.44 - return 0; 23.45 - 23.46 - int result; 23.47 - switch (criteria.field) { 23.48 - case DONE: 23.49 - result = Boolean.compare( 23.50 - left.getStatus().getPhase().equals(IssueStatusPhase.Companion.getDone()), 23.51 - right.getStatus().getPhase().equals(IssueStatusPhase.Companion.getDone())); 23.52 - break; 23.53 - case ETA: 23.54 - if (left.getEta() != null && right.getEta() != null) 23.55 - result = left.getEta().compareTo(right.getEta()); 23.56 - else if (left.getEta() == null && right.getEta() == null) 23.57 - result = 0; 23.58 - else 23.59 - result = left.getEta() != null ? -1 : 1; 23.60 - break; 23.61 - case UPDATED: 23.62 - result = left.getUpdated().compareTo(right.getUpdated()); 23.63 - break; 23.64 - default: 23.65 - throw new UnsupportedOperationException(); 23.66 - } 23.67 - return criteria.asc ? result : -result; 23.68 - } 23.69 - 23.70 - @Override 23.71 - public int compare(Issue left, Issue right) { 23.72 - for (var c : criteria) { 23.73 - int r = compare(left, right, c); 23.74 - if (r != 0) return r; 23.75 - } 23.76 - return 0; 23.77 - } 23.78 - 23.79 - @Override 23.80 - public boolean equals(Object o) { 23.81 - if (o == null || !o.getClass().equals(IssueSorter.class)) 23.82 - return false; 23.83 - final var other = (IssueSorter) o; 23.84 - return Arrays.equals(criteria, other.criteria); 23.85 - } 23.86 -}
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 24.2 +++ b/src/main/kotlin/de/uapcore/lightpit/AbstractServlet.kt Fri Apr 02 11:59:14 2021 +0200 24.3 @@ -0,0 +1,182 @@ 24.4 +/* 24.5 + * Copyright 2021 Mike Becker. All rights reserved. 24.6 + * 24.7 + * Redistribution and use in source and binary forms, with or without 24.8 + * modification, are permitted provided that the following conditions are met: 24.9 + * 24.10 + * 1. Redistributions of source code must retain the above copyright 24.11 + * notice, this list of conditions and the following disclaimer. 24.12 + * 24.13 + * 2. Redistributions in binary form must reproduce the above copyright 24.14 + * notice, this list of conditions and the following disclaimer in the 24.15 + * documentation and/or other materials provided with the distribution. 24.16 + * 24.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24.27 + */ 24.28 + 24.29 +package de.uapcore.lightpit 24.30 + 24.31 +import de.uapcore.lightpit.DataSourceProvider.Companion.SC_ATTR_NAME 24.32 +import de.uapcore.lightpit.dao.DataAccessObject 24.33 +import de.uapcore.lightpit.dao.createDataAccessObject 24.34 +import java.sql.SQLException 24.35 +import java.util.* 24.36 +import javax.servlet.http.HttpServlet 24.37 +import javax.servlet.http.HttpServletRequest 24.38 +import javax.servlet.http.HttpServletResponse 24.39 + 24.40 +abstract class AbstractServlet : LoggingTrait, HttpServlet() { 24.41 + 24.42 + /** 24.43 + * Contains the GET request mappings. 24.44 + */ 24.45 + private val getMappings = mutableMapOf<PathPattern, MappingMethod>() 24.46 + 24.47 + /** 24.48 + * Contains the POST request mappings. 24.49 + */ 24.50 + private val postMappings = mutableMapOf<PathPattern, MappingMethod>() 24.51 + 24.52 + protected fun get(pattern: String, method: MappingMethod) { 24.53 + getMappings[PathPattern(pattern)] = method 24.54 + } 24.55 + 24.56 + protected fun post(pattern: String, method: MappingMethod) { 24.57 + postMappings[PathPattern(pattern)] = method 24.58 + } 24.59 + 24.60 + private fun notFound(http: HttpRequest, dao: DataAccessObject) { 24.61 + http.response.sendError(HttpServletResponse.SC_NOT_FOUND) 24.62 + } 24.63 + 24.64 + private fun findMapping( 24.65 + mappings: Map<PathPattern, MappingMethod>, 24.66 + req: HttpServletRequest 24.67 + ): Pair<PathPattern, MappingMethod> { 24.68 + val requestPath = sanitizedRequestPath(req) 24.69 + val candidates = mappings.filter { it.key.matches(requestPath) } 24.70 + return if (candidates.isEmpty()) { 24.71 + Pair(PathPattern(requestPath), ::notFound) 24.72 + } else { 24.73 + if (candidates.size > 1) { 24.74 + logger().warn("Ambiguous mapping for request path '{}'", requestPath) 24.75 + } 24.76 + candidates.entries.first().toPair() 24.77 + } 24.78 + } 24.79 + 24.80 + private fun invokeMapping( 24.81 + mapping: Pair<PathPattern, MappingMethod>, 24.82 + req: HttpServletRequest, 24.83 + resp: HttpServletResponse, 24.84 + dao: DataAccessObject 24.85 + ) { 24.86 + val params = mapping.first.obtainPathParameters(sanitizedRequestPath(req)) 24.87 + val method = mapping.second 24.88 + logger().trace("invoke {}", method) 24.89 + method(HttpRequest(req, resp, params), dao) 24.90 + } 24.91 + 24.92 + private fun sanitizedRequestPath(req: HttpServletRequest) = req.pathInfo ?: "/" 24.93 + 24.94 + private fun doProcess( 24.95 + req: HttpServletRequest, 24.96 + resp: HttpServletResponse, 24.97 + mappings: Map<PathPattern, MappingMethod> 24.98 + ) { 24.99 + val session = req.session 24.100 + 24.101 + // the very first thing to do is to force UTF-8 24.102 + req.characterEncoding = "UTF-8" 24.103 + 24.104 + // choose the requested language as session language (if available) or fall back to english, otherwise 24.105 + if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { 24.106 + val availableLanguages = availableLanguages() 24.107 + val reqLocale = req.locale 24.108 + val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first() 24.109 + session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale) 24.110 + logger().debug( 24.111 + "Setting language for new session {}: {}", session.id, sessionLocale.displayLanguage 24.112 + ) 24.113 + } else { 24.114 + val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale 24.115 + resp.locale = sessionLocale 24.116 + logger().trace("Continuing session {} with language {}", session.id, sessionLocale) 24.117 + } 24.118 + 24.119 + // set some internal request attributes 24.120 + val http = HttpRequest(req, resp) 24.121 + val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("") 24.122 + req.setAttribute(Constants.REQ_ATTR_BASE_HREF, http.baseHref) 24.123 + req.setAttribute(Constants.REQ_ATTR_PATH, fullPath) 24.124 + req.getHeader("Referer")?.let { 24.125 + // TODO: add a sanity check to avoid link injection 24.126 + req.setAttribute(Constants.REQ_ATTR_REFERER, it) 24.127 + } 24.128 + 24.129 + // if this is an error path, bypass the normal flow 24.130 + if (fullPath.startsWith("/error/")) { 24.131 + http.styleSheets = listOf("error") 24.132 + http.render("error") 24.133 + return 24.134 + } 24.135 + 24.136 + // obtain a connection and create the data access objects 24.137 + val dsp = req.servletContext.getAttribute(SC_ATTR_NAME) as DataSourceProvider 24.138 + val dialect = dsp.dialect 24.139 + val ds = dsp.dataSource 24.140 + if (ds == null) { 24.141 + resp.sendError( 24.142 + HttpServletResponse.SC_SERVICE_UNAVAILABLE, 24.143 + "JNDI DataSource lookup failed. See log for details." 24.144 + ) 24.145 + return 24.146 + } 24.147 + try { 24.148 + ds.connection.use { connection -> 24.149 + val dao = createDataAccessObject(dialect, connection) 24.150 + try { 24.151 + connection.autoCommit = false 24.152 + invokeMapping(findMapping(mappings, req), req, resp, dao) 24.153 + connection.commit() 24.154 + } catch (ex: SQLException) { 24.155 + logger().warn("Database transaction failed (Code {}): {}", ex.errorCode, ex.message) 24.156 + logger().debug("Details: ", ex) 24.157 + resp.sendError( 24.158 + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 24.159 + "Unhandled Transaction Error - Code: " + ex.errorCode 24.160 + ) 24.161 + connection.rollback() 24.162 + } 24.163 + } 24.164 + } catch (ex: SQLException) { 24.165 + logger().error("Severe Database Exception (Code {}): {}", ex.errorCode, ex.message) 24.166 + logger().debug("Details: ", ex) 24.167 + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.errorCode) 24.168 + } 24.169 + } 24.170 + 24.171 + override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { 24.172 + doProcess(req, resp, getMappings) 24.173 + } 24.174 + 24.175 + override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { 24.176 + doProcess(req, resp, postMappings) 24.177 + } 24.178 + 24.179 + protected fun availableLanguages(): List<Locale> { 24.180 + val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList() 24.181 + val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() } 24.182 + return if (locales.isEmpty()) listOf(Locale.ENGLISH) else locales 24.183 + } 24.184 + 24.185 +}
25.1 --- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt Sat Jan 23 14:47:59 2021 +0100 25.2 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt Fri Apr 02 11:59:14 2021 +0200 25.3 @@ -52,7 +52,7 @@ 25.4 const val CTX_ATTR_DB_DIALECT = "db-dialect" 25.5 25.6 /** 25.7 - * Key for the request attribute containing the optional navigation menu jsp. 25.8 + * Key for the request attribute containing the optional navigation menu. 25.9 */ 25.10 const val REQ_ATTR_NAVIGATION = "navMenu" 25.11 25.12 @@ -88,6 +88,11 @@ 25.13 const val REQ_ATTR_REDIRECT_LOCATION = "redirectLocation" 25.14 25.15 /** 25.16 + * Key for the optional return link based on the referer header. 25.17 + */ 25.18 + const val REQ_ATTR_REFERER = "returnLink" 25.19 + 25.20 + /** 25.21 * Key for the current language selection within the session. 25.22 */ 25.23 const val SESSION_ATTR_LANGUAGE = "language"
26.1 --- a/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt Sat Jan 23 14:47:59 2021 +0100 26.2 +++ b/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt Fri Apr 02 11:59:14 2021 +0200 26.3 @@ -61,12 +61,12 @@ 26.4 /** 26.5 * The attribute name in the Servlet context under which an instance of this class can be found. 26.6 */ 26.7 - val SC_ATTR_NAME = "lightpit.service.DataSourceProvider" 26.8 + const val SC_ATTR_NAME = "lightpit.service.DataSourceProvider" 26.9 26.10 /** 26.11 * Timeout in seconds for the validation test. 26.12 */ 26.13 - private val DB_TEST_TIMEOUT = 10 26.14 + private const val DB_TEST_TIMEOUT = 10 26.15 26.16 /** 26.17 * The default schema to test against when validating the connection. 26.18 @@ -74,12 +74,12 @@ 26.19 * 26.20 * @see Constants.CTX_ATTR_DB_SCHEMA 26.21 */ 26.22 - private val DB_DEFAULT_SCHEMA = "lightpit" 26.23 + private const val DB_DEFAULT_SCHEMA = "lightpit" 26.24 26.25 /** 26.26 * The JNDI resource name for the data source. 26.27 */ 26.28 - private val DS_JNDI_NAME = "jdbc/lightpit/app" 26.29 + private const val DS_JNDI_NAME = "jdbc/lightpit/app" 26.30 } 26.31 26.32 private fun checkConnection(ds: DataSource, testSchema: String) {
27.1 --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat Jan 23 14:47:59 2021 +0100 27.2 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Fri Apr 02 11:59:14 2021 +0200 27.3 @@ -25,45 +25,106 @@ 27.4 27.5 package de.uapcore.lightpit 27.6 27.7 +import de.uapcore.lightpit.dao.DataAccessObject 27.8 +import de.uapcore.lightpit.viewmodel.NavMenu 27.9 +import de.uapcore.lightpit.viewmodel.View 27.10 +import javax.servlet.http.HttpServletRequest 27.11 +import javax.servlet.http.HttpServletResponse 27.12 +import javax.servlet.http.HttpSession 27.13 import kotlin.math.min 27.14 27.15 -/** 27.16 - * Maps requests to methods. 27.17 - * 27.18 - * This annotation is used to annotate methods within classes which 27.19 - * override [AbstractServlet]. 27.20 - */ 27.21 -@MustBeDocumented 27.22 -@Retention(AnnotationRetention.RUNTIME) 27.23 -@Target(AnnotationTarget.FUNCTION) 27.24 -annotation class RequestMapping( 27.25 +typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit 27.26 +typealias PathParameters = Map<String, String> 27.27 + 27.28 +class HttpRequest( 27.29 + val request: HttpServletRequest, 27.30 + val response: HttpServletResponse, 27.31 + val pathParams: PathParameters = emptyMap() 27.32 +) { 27.33 + val session: HttpSession = request.session 27.34 + 27.35 + val remoteUser: String? = request.remoteUser 27.36 27.37 /** 27.38 - * Specifies the HTTP method. 27.39 + * The name of the content page. 27.40 * 27.41 - * @return the HTTP method handled by the annotated Java method 27.42 + * @see Constants#REQ_ATTR_CONTENT_PAGE 27.43 */ 27.44 - val method: HttpMethod, 27.45 + var contentPage = "" 27.46 + set(value) { 27.47 + field = value 27.48 + request.setAttribute(Constants.REQ_ATTR_CONTENT_PAGE, jspPath(value)) 27.49 + } 27.50 27.51 /** 27.52 - * Specifies the request path relative to the module path. 27.53 - * The trailing slash is important. 27.54 - * A node may start with a dollar ($) sign. 27.55 - * This part of the path is then treated as an path parameter. 27.56 - * Path parameters can be obtained by including the [PathParameters] type in the signature. 27.57 + * A list of additional style sheets. 27.58 * 27.59 - * @return the request path the annotated method should handle 27.60 + * @see Constants#REQ_ATTR_STYLESHEET 27.61 */ 27.62 - val requestPath: String = "/" 27.63 -) 27.64 + var styleSheets = emptyList<String>() 27.65 + set(value) { 27.66 + field = value 27.67 + request.setAttribute(Constants.REQ_ATTR_STYLESHEET, 27.68 + value.map { it.withExt(".css") } 27.69 + ) 27.70 + } 27.71 27.72 -class PathParameters : HashMap<String, String>() 27.73 + /** 27.74 + * The name of the navigation menu JSP. 27.75 + * 27.76 + * @see Constants#REQ_ATTR_NAVIGATION 27.77 + */ 27.78 + var navigationMenu: NavMenu? = null 27.79 + set(value) { 27.80 + field = value 27.81 + request.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationMenu) 27.82 + } 27.83 + 27.84 + var redirectLocation = "" 27.85 + set(value) { 27.86 + field = value 27.87 + request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value) 27.88 + } 27.89 + 27.90 + /** 27.91 + * The view object. 27.92 + * 27.93 + * @see Constants#REQ_ATTR_VIEWMODEL 27.94 + */ 27.95 + var view: View? = null 27.96 + set(value) { 27.97 + field = value 27.98 + request.setAttribute(Constants.REQ_ATTR_VIEWMODEL, value) 27.99 + } 27.100 + 27.101 + /** 27.102 + * The base path of this application. 27.103 + */ 27.104 + val baseHref get() = "${request.scheme}://${request.serverName}:${request.serverPort}${request.contextPath}/" 27.105 + 27.106 + private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext) 27.107 + private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp") 27.108 + 27.109 + fun param(name: String): String? = request.getParameter(name) 27.110 + fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray() 27.111 + 27.112 + fun render(page: String? = null) { 27.113 + page?.let { contentPage = it } 27.114 + request.getRequestDispatcher(jspPath("site")).forward(request, response) 27.115 + } 27.116 + 27.117 + fun renderCommit(location: String? = null) { 27.118 + location?.let { redirectLocation = it } 27.119 + contentPage = Constants.JSP_COMMIT_SUCCESSFUL 27.120 + render() 27.121 + } 27.122 +} 27.123 27.124 /** 27.125 * A path pattern optionally containing placeholders. 27.126 * 27.127 * The special directories . and .. are disallowed in the pattern. 27.128 - * Placeholders start with a $ sign. 27.129 + * Placeholders start with a % sign. 27.130 * 27.131 * @param pattern the pattern 27.132 */ 27.133 @@ -91,7 +152,7 @@ 27.134 for (i in nodePatterns.indices) { 27.135 val pattern = nodePatterns[i] 27.136 val node = nodes[i] 27.137 - if (pattern.startsWith("$")) continue 27.138 + if (pattern.startsWith("%")) continue 27.139 if (pattern != node) return false 27.140 } 27.141 return true 27.142 @@ -106,12 +167,12 @@ 27.143 * @see .matches 27.144 */ 27.145 fun obtainPathParameters(path: String): PathParameters { 27.146 - val params = PathParameters() 27.147 + val params = mutableMapOf<String, String>() 27.148 val nodes = parse(path) 27.149 for (i in 0 until min(nodes.size, nodePatterns.size)) { 27.150 val pattern = nodePatterns[i] 27.151 val node = nodes[i] 27.152 - if (pattern.startsWith("$")) { 27.153 + if (pattern.startsWith("%")) { 27.154 params[pattern.substring(1)] = node 27.155 } 27.156 } 27.157 @@ -121,8 +182,8 @@ 27.158 override fun hashCode(): Int { 27.159 val str = StringBuilder() 27.160 for (node in nodePatterns) { 27.161 - if (node.startsWith("$")) { 27.162 - str.append("/$") 27.163 + if (node.startsWith("%")) { 27.164 + str.append("/%") 27.165 } else { 27.166 str.append('/') 27.167 str.append(node) 27.168 @@ -138,7 +199,7 @@ 27.169 for (i in nodePatterns.indices) { 27.170 val left = nodePatterns[i] 27.171 val right = other.nodePatterns[i] 27.172 - if (left.startsWith("$") && right.startsWith("$")) continue 27.173 + if (left.startsWith("%") && right.startsWith("%")) continue 27.174 if (left != right) return false 27.175 } 27.176 return true
28.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 28.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DaoFactory.kt Fri Apr 02 11:59:14 2021 +0200 28.3 @@ -0,0 +1,37 @@ 28.4 +/* 28.5 + * Copyright 2021 Mike Becker. All rights reserved. 28.6 + * 28.7 + * Redistribution and use in source and binary forms, with or without 28.8 + * modification, are permitted provided that the following conditions are met: 28.9 + * 28.10 + * 1. Redistributions of source code must retain the above copyright 28.11 + * notice, this list of conditions and the following disclaimer. 28.12 + * 28.13 + * 2. Redistributions in binary form must reproduce the above copyright 28.14 + * notice, this list of conditions and the following disclaimer in the 28.15 + * documentation and/or other materials provided with the distribution. 28.16 + * 28.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28.27 + */ 28.28 + 28.29 +package de.uapcore.lightpit.dao 28.30 + 28.31 +import de.uapcore.lightpit.DataSourceProvider 28.32 +import java.sql.Connection 28.33 + 28.34 +fun createDataAccessObject( 28.35 + dialect: DataSourceProvider.Dialect, 28.36 + connection: Connection 28.37 +): DataAccessObject = 28.38 + when (dialect) { 28.39 + DataSourceProvider.Dialect.Postgres -> PostgresDataAccessObject(connection) 28.40 + } 28.41 \ No newline at end of file
29.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sat Jan 23 14:47:59 2021 +0100 29.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Fri Apr 02 11:59:14 2021 +0200 29.3 @@ -26,7 +26,10 @@ 29.4 package de.uapcore.lightpit.dao 29.5 29.6 import de.uapcore.lightpit.entities.* 29.7 -import de.uapcore.lightpit.filter.IssueFilter 29.8 +import de.uapcore.lightpit.util.IssueFilter 29.9 +import de.uapcore.lightpit.viewmodel.ComponentSummary 29.10 +import de.uapcore.lightpit.viewmodel.IssueSummary 29.11 +import de.uapcore.lightpit.viewmodel.VersionSummary 29.12 29.13 interface DataAccessObject { 29.14 fun listUsers(): List<User> 29.15 @@ -35,18 +38,29 @@ 29.16 fun insertUser(user: User) 29.17 fun updateUser(user: User) 29.18 29.19 + /** 29.20 + * Lists all versions of the specified [project]. 29.21 + * 29.22 + * The list is first ordered by the ordinal of the version and 29.23 + * then by name, both descending. 29.24 + */ 29.25 fun listVersions(project: Project): List<Version> 29.26 + fun listVersionSummaries(project: Project): List<VersionSummary> 29.27 fun findVersion(id: Int): Version? 29.28 fun findVersionByNode(project: Project, node: String): Version? 29.29 fun insertVersion(version: Version) 29.30 fun updateVersion(version: Version) 29.31 29.32 fun listComponents(project: Project): List<Component> 29.33 + fun listComponentSummaries(project: Project): List<ComponentSummary> 29.34 fun findComponent(id: Int): Component? 29.35 fun findComponentByNode(project: Project, node: String): Component? 29.36 fun insertComponent(component: Component) 29.37 fun updateComponent(component: Component) 29.38 29.39 + /** 29.40 + * Lists all projects ordered by name. 29.41 + */ 29.42 fun listProjects(): List<Project> 29.43 fun findProject(id: Int): Project? 29.44 fun findProjectByNode(node: String): Project? 29.45 @@ -57,7 +71,7 @@ 29.46 29.47 fun listIssues(filter: IssueFilter): List<Issue> 29.48 fun findIssue(id: Int): Issue? 29.49 - fun insertIssue(issue: Issue) 29.50 + fun insertIssue(issue: Issue): Int 29.51 fun updateIssue(issue: Issue) 29.52 29.53 fun listComments(issue: Issue): List<IssueComment>
30.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sat Jan 23 14:47:59 2021 +0100 30.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Fri Apr 02 11:59:14 2021 +0200 30.3 @@ -26,8 +26,11 @@ 30.4 package de.uapcore.lightpit.dao 30.5 30.6 import de.uapcore.lightpit.entities.* 30.7 -import de.uapcore.lightpit.filter.* 30.8 import de.uapcore.lightpit.types.WebColor 30.9 +import de.uapcore.lightpit.util.* 30.10 +import de.uapcore.lightpit.viewmodel.ComponentSummary 30.11 +import de.uapcore.lightpit.viewmodel.IssueSummary 30.12 +import de.uapcore.lightpit.viewmodel.VersionSummary 30.13 import java.sql.Connection 30.14 import java.sql.PreparedStatement 30.15 import java.sql.ResultSet 30.16 @@ -129,15 +132,19 @@ 30.17 //</editor-fold> 30.18 30.19 //<editor-fold desc="Version"> 30.20 + 30.21 + private fun obtainVersion(rs: ResultSet) = 30.22 + Version(rs.getInt("versionid"), rs.getInt("project")).apply { 30.23 + name = rs.getString("name") 30.24 + node = rs.getString("node") 30.25 + ordinal = rs.getInt("ordinal") 30.26 + status = rs.getEnum("status") 30.27 + } 30.28 + 30.29 private fun selectVersions(stmt: PreparedStatement) = sequence { 30.30 stmt.executeQuery().use { rs -> 30.31 while (rs.next()) { 30.32 - yield(Version(rs.getInt("versionid"), rs.getInt("project")).apply { 30.33 - name = rs.getString("name") 30.34 - node = rs.getString("node") 30.35 - ordinal = rs.getInt("ordinal") 30.36 - status = rs.getEnum("status") 30.37 - }) 30.38 + yield(obtainVersion(rs)) 30.39 } 30.40 } 30.41 } 30.42 @@ -163,6 +170,36 @@ 30.43 """ 30.44 ) 30.45 } 30.46 + private val stmtVersionSummaries by lazy { 30.47 + connection.prepareStatement( 30.48 + """ 30.49 + with version_map(issueid, versionid, isresolved) as ( 30.50 + select issueid, versionid, 1 30.51 + from lpit_issue_resolved_version 30.52 + union 30.53 + select issueid, versionid, 0 30.54 + from lpit_issue_affected_version 30.55 + ), 30.56 + issues as ( 30.57 + select versionid, phase, isresolved, count(issueid) as total 30.58 + from lpit_issue 30.59 + join version_map using (issueid) 30.60 + join lpit_issue_phases using (status) 30.61 + group by versionid, phase, isresolved 30.62 + ), 30.63 + summary as ( 30.64 + select versionid, phase, isresolved, total 30.65 + from lpit_version v 30.66 + left join issues using (versionid) 30.67 + where v.project = ? 30.68 + ) 30.69 + select versionid, project, name, node, ordinal, status, phase, isresolved, total 30.70 + from lpit_version 30.71 + join summary using (versionid) 30.72 + order by ordinal, name 30.73 + """ 30.74 + ) 30.75 + } 30.76 private val stmtVersionByID by lazy { 30.77 connection.prepareStatement( 30.78 """${versionQuery} 30.79 @@ -199,6 +236,27 @@ 30.80 return selectVersions(stmtVersions).toList() 30.81 } 30.82 30.83 + override fun listVersionSummaries(project: Project): List<VersionSummary> { 30.84 + stmtVersionSummaries.setInt(1, project.id) 30.85 + return sequence { 30.86 + stmtVersionSummaries.executeQuery().use { rs -> 30.87 + while (rs.next()) { 30.88 + val versionSummary = VersionSummary(obtainVersion(rs)) 30.89 + val phase = rs.getInt("phase") 30.90 + val total = rs.getInt("total") 30.91 + val issueSummary = 30.92 + if (rs.getBoolean("isresolved")) versionSummary.resolvedTotal else versionSummary.reportedTotal 30.93 + when (phase) { 30.94 + 0 -> issueSummary.open = total 30.95 + 1 -> issueSummary.active = total 30.96 + 2 -> issueSummary.done = total 30.97 + } 30.98 + yield(versionSummary) 30.99 + } 30.100 + } 30.101 + }.toList() 30.102 + } 30.103 + 30.104 override fun findVersion(id: Int): Version? { 30.105 stmtVersionByID.setInt(1, id) 30.106 return selectVersions(stmtVersionByID).firstOrNull() 30.107 @@ -224,21 +282,25 @@ 30.108 //</editor-fold> 30.109 30.110 //<editor-fold desc="Component"> 30.111 + 30.112 + private fun obtainComponent(rs: ResultSet): Component = 30.113 + Component(rs.getInt("id"), rs.getInt("project")).apply { 30.114 + name = rs.getString("name") 30.115 + node = rs.getString("node") 30.116 + color = try { 30.117 + WebColor(rs.getString("color")) 30.118 + } catch (ex: IllegalArgumentException) { 30.119 + WebColor("000000") 30.120 + } 30.121 + ordinal = rs.getInt("ordinal") 30.122 + description = rs.getString("description") 30.123 + lead = selectUserInfo(rs) 30.124 + } 30.125 + 30.126 private fun selectComponents(stmt: PreparedStatement) = sequence { 30.127 stmt.executeQuery().use { rs -> 30.128 while (rs.next()) { 30.129 - yield(Component(rs.getInt("id"), rs.getInt("project")).apply { 30.130 - name = rs.getString("name") 30.131 - node = rs.getString("node") 30.132 - color = try { 30.133 - WebColor(rs.getString("color")) 30.134 - } catch (ex: IllegalArgumentException) { 30.135 - WebColor("000000") 30.136 - } 30.137 - ordinal = rs.getInt("ordinal") 30.138 - description = rs.getString("description") 30.139 - lead = selectUserInfo(rs) 30.140 - }) 30.141 + yield(obtainComponent(rs)) 30.142 } 30.143 } 30.144 } 30.145 @@ -272,6 +334,30 @@ 30.146 """ 30.147 ) 30.148 } 30.149 + private val stmtComponentSummaries by lazy { 30.150 + connection.prepareStatement( 30.151 + """ 30.152 + with issues as ( 30.153 + select component, phase, count(issueid) as total 30.154 + from lpit_issue 30.155 + join lpit_issue_phases using (status) 30.156 + group by component, phase 30.157 + ), 30.158 + summary as ( 30.159 + select c.id, phase, total 30.160 + from lpit_component c 30.161 + left join issues i on c.id = i.component 30.162 + where c.project = ? 30.163 + ) 30.164 + select c.id, project, name, node, color, ordinal, description, 30.165 + userid, username, givenname, lastname, mail, phase, total 30.166 + from lpit_component c 30.167 + left join lpit_user on lead = userid 30.168 + join summary s on c.id = s.id 30.169 + order by ordinal, name 30.170 + """ 30.171 + ) 30.172 + } 30.173 private val stmtComponentById by lazy { 30.174 connection.prepareStatement( 30.175 """${componentQuery} 30.176 @@ -305,6 +391,25 @@ 30.177 return selectComponents(stmtComponents).toList() 30.178 } 30.179 30.180 + override fun listComponentSummaries(project: Project): List<ComponentSummary> { 30.181 + stmtComponentSummaries.setInt(1, project.id) 30.182 + return sequence { 30.183 + stmtComponentSummaries.executeQuery().use { rs -> 30.184 + while (rs.next()) { 30.185 + val componentSummary = ComponentSummary(obtainComponent(rs)) 30.186 + val phase = rs.getInt("phase") 30.187 + val total = rs.getInt("total") 30.188 + when (phase) { 30.189 + 0 -> componentSummary.issueSummary.open = total 30.190 + 1 -> componentSummary.issueSummary.active = total 30.191 + 2 -> componentSummary.issueSummary.done = total 30.192 + } 30.193 + yield(componentSummary) 30.194 + } 30.195 + } 30.196 + }.toList() 30.197 + } 30.198 + 30.199 override fun findComponent(id: Int): Component? { 30.200 stmtComponentById.setInt(1, id) 30.201 return selectComponents(stmtComponentById).firstOrNull() 30.202 @@ -471,7 +576,7 @@ 30.203 node = rs.getString("componentnode") 30.204 } 30.205 } 30.206 - val issue = Issue(rs.getInt("issueid"), proj, comp).apply { 30.207 + val issue = Issue(rs.getInt("issueid"), proj).apply { 30.208 component = comp 30.209 status = rs.getEnum("status") 30.210 category = rs.getEnum("category") 30.211 @@ -672,14 +777,15 @@ 30.212 } 30.213 } 30.214 30.215 - override fun insertIssue(issue: Issue) { 30.216 + override fun insertIssue(issue: Issue): Int { 30.217 val col = setIssueFields(stmtInsertIssue, issue) 30.218 stmtInsertIssue.setInt(col, issue.project.id) 30.219 - stmtInsertIssue.executeQuery().use { rs -> 30.220 + val id = stmtInsertIssue.executeQuery().use { rs -> 30.221 rs.next() 30.222 - issue.id = rs.getInt(1) 30.223 + rs.getInt(1) 30.224 } 30.225 insertVersionInfo(issue) 30.226 + return id 30.227 } 30.228 30.229 override fun updateIssue(issue: Issue) {
31.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt Sat Jan 23 14:47:59 2021 +0100 31.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt Fri Apr 02 11:59:14 2021 +0200 31.3 @@ -27,11 +27,11 @@ 31.4 31.5 import de.uapcore.lightpit.types.WebColor 31.6 31.7 -data class Component(override val id: Int, var projectid: Int) : Entity { 31.8 +data class Component(override val id: Int, val projectid: Int) : Entity, HasNode { 31.9 var name: String = "" 31.10 - var node: String = name 31.11 + override var node: String = name 31.12 + var ordinal = 0 31.13 var color = WebColor("000000") 31.14 - var ordinal = 0 31.15 var description: String? = null 31.16 var lead: User? = null 31.17 } 31.18 \ No newline at end of file
32.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Entity.kt Sat Jan 23 14:47:59 2021 +0100 32.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Entity.kt Fri Apr 02 11:59:14 2021 +0200 32.3 @@ -27,4 +27,8 @@ 32.4 32.5 interface Entity { 32.6 val id: Int 32.7 -} 32.8 \ No newline at end of file 32.9 +} 32.10 + 32.11 +interface HasNode { 32.12 + val node: String 32.13 +}
33.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt Sat Jan 23 14:47:59 2021 +0100 33.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt Fri Apr 02 11:59:14 2021 +0200 33.3 @@ -32,13 +32,13 @@ 33.4 import java.sql.Timestamp 33.5 import java.time.Instant 33.6 33.7 -data class Issue(override var id: Int, var project: Project, var component: Component? = null) : Entity { 33.8 - 33.9 +data class Issue(override val id: Int, var project: Project) : Entity { 33.10 + var component: Component? = null 33.11 var status = IssueStatus.InSpecification 33.12 var category = IssueCategory.Feature 33.13 33.14 var subject: String = "" 33.15 - var description: String? = null 33.16 + var description: String = "" 33.17 var assignee: User? = null 33.18 33.19 var created: Timestamp = Timestamp.from(Instant.now())
34.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueSummary.kt Sat Jan 23 14:47:59 2021 +0100 34.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 34.3 @@ -1,53 +0,0 @@ 34.4 -/* 34.5 - * Copyright 2021 Mike Becker. All rights reserved. 34.6 - * 34.7 - * Redistribution and use in source and binary forms, with or without 34.8 - * modification, are permitted provided that the following conditions are met: 34.9 - * 34.10 - * 1. Redistributions of source code must retain the above copyright 34.11 - * notice, this list of conditions and the following disclaimer. 34.12 - * 34.13 - * 2. Redistributions in binary form must reproduce the above copyright 34.14 - * notice, this list of conditions and the following disclaimer in the 34.15 - * documentation and/or other materials provided with the distribution. 34.16 - * 34.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 34.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 34.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 34.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 34.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 34.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34.27 - */ 34.28 - 34.29 -package de.uapcore.lightpit.entities 34.30 - 34.31 -import de.uapcore.lightpit.types.IssueStatusPhase 34.32 -import kotlin.math.roundToInt 34.33 - 34.34 -class IssueSummary { 34.35 - var open = 0 34.36 - var active = 0 34.37 - var done = 0 34.38 - 34.39 - val total get() = open + active + done 34.40 - 34.41 - val openPercent get() = 100 - activePercent - donePercent 34.42 - val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0 34.43 - val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100 34.44 - 34.45 - /** 34.46 - * Adds the specified issue to the summary by incrementing the respective counter. 34.47 - * @param issue the issue 34.48 - */ 34.49 - fun add(issue: Issue) { 34.50 - when (issue.status.phase) { 34.51 - IssueStatusPhase.Open -> open++ 34.52 - IssueStatusPhase.WorkInProgress -> active++ 34.53 - IssueStatusPhase.Done -> done++ 34.54 - } 34.55 - } 34.56 -} 34.57 \ No newline at end of file
35.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sat Jan 23 14:47:59 2021 +0100 35.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Fri Apr 02 11:59:14 2021 +0200 35.3 @@ -25,9 +25,9 @@ 35.4 35.5 package de.uapcore.lightpit.entities 35.6 35.7 -data class Project(override val id: Int) : Entity { 35.8 +data class Project(override val id: Int) : Entity, HasNode { 35.9 var name: String = "" 35.10 - var node: String = name 35.11 + override var node: String = name 35.12 var ordinal = 0 35.13 var description: String? = null 35.14 var repoUrl: String? = null
36.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Sat Jan 23 14:47:59 2021 +0100 36.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Fri Apr 02 11:59:14 2021 +0200 36.3 @@ -27,9 +27,9 @@ 36.4 36.5 import de.uapcore.lightpit.types.VersionStatus 36.6 36.7 -data class Version(override val id: Int, var projectid: Int) : Entity, Comparable<Version> { 36.8 +data class Version(override val id: Int, val projectid: Int) : Entity, HasNode, Comparable<Version> { 36.9 var name: String = "" 36.10 - var node = name 36.11 + override var node = name 36.12 var ordinal = 0 36.13 var status = VersionStatus.Future 36.14
37.1 --- a/src/main/kotlin/de/uapcore/lightpit/filter/Filter.kt Sat Jan 23 14:47:59 2021 +0100 37.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 37.3 @@ -1,32 +0,0 @@ 37.4 -/* 37.5 - * Copyright 2021 Mike Becker. All rights reserved. 37.6 - * 37.7 - * Redistribution and use in source and binary forms, with or without 37.8 - * modification, are permitted provided that the following conditions are met: 37.9 - * 37.10 - * 1. Redistributions of source code must retain the above copyright 37.11 - * notice, this list of conditions and the following disclaimer. 37.12 - * 37.13 - * 2. Redistributions in binary form must reproduce the above copyright 37.14 - * notice, this list of conditions and the following disclaimer in the 37.15 - * documentation and/or other materials provided with the distribution. 37.16 - * 37.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 37.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 37.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 37.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 37.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 37.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37.27 - */ 37.28 - 37.29 -package de.uapcore.lightpit.filter 37.30 - 37.31 -sealed class Filter<T> 37.32 -class AllFilter<T> : Filter<T>() 37.33 -class NoneFilter<T> : Filter<T>() 37.34 -data class SpecificFilter<T>(val obj: T) : Filter<T>() 37.35 -data class RangeFilter<T>(val lower: T, val upper: T) : Filter<T>() where T : Comparable<T>
38.1 --- a/src/main/kotlin/de/uapcore/lightpit/filter/IssueFilter.kt Sat Jan 23 14:47:59 2021 +0100 38.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 38.3 @@ -1,36 +0,0 @@ 38.4 -/* 38.5 - * Copyright 2021 Mike Becker. All rights reserved. 38.6 - * 38.7 - * Redistribution and use in source and binary forms, with or without 38.8 - * modification, are permitted provided that the following conditions are met: 38.9 - * 38.10 - * 1. Redistributions of source code must retain the above copyright 38.11 - * notice, this list of conditions and the following disclaimer. 38.12 - * 38.13 - * 2. Redistributions in binary form must reproduce the above copyright 38.14 - * notice, this list of conditions and the following disclaimer in the 38.15 - * documentation and/or other materials provided with the distribution. 38.16 - * 38.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 38.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 38.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 38.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 38.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 38.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 38.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38.27 - */ 38.28 - 38.29 -package de.uapcore.lightpit.filter 38.30 - 38.31 -import de.uapcore.lightpit.entities.Component 38.32 -import de.uapcore.lightpit.entities.Project 38.33 -import de.uapcore.lightpit.entities.Version 38.34 - 38.35 -data class IssueFilter( 38.36 - val project: Filter<Project> = AllFilter(), 38.37 - val version: Filter<Version> = AllFilter(), 38.38 - val component: Filter<Component> = AllFilter() 38.39 -)
39.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 39.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ErrorServlet.kt Fri Apr 02 11:59:14 2021 +0200 39.3 @@ -0,0 +1,32 @@ 39.4 +/* 39.5 + * Copyright 2021 Mike Becker. All rights reserved. 39.6 + * 39.7 + * Redistribution and use in source and binary forms, with or without 39.8 + * modification, are permitted provided that the following conditions are met: 39.9 + * 39.10 + * 1. Redistributions of source code must retain the above copyright 39.11 + * notice, this list of conditions and the following disclaimer. 39.12 + * 39.13 + * 2. Redistributions in binary form must reproduce the above copyright 39.14 + * notice, this list of conditions and the following disclaimer in the 39.15 + * documentation and/or other materials provided with the distribution. 39.16 + * 39.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 39.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 39.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 39.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 39.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 39.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 39.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 39.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39.27 + */ 39.28 + 39.29 +package de.uapcore.lightpit.servlet 39.30 + 39.31 +import de.uapcore.lightpit.AbstractServlet 39.32 +import javax.servlet.annotation.WebServlet 39.33 + 39.34 +@WebServlet(urlPatterns = ["/error/*"]) 39.35 +class ErrorServlet : AbstractServlet() 39.36 \ No newline at end of file
40.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 40.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/LanguageServlet.kt Fri Apr 02 11:59:14 2021 +0200 40.3 @@ -0,0 +1,69 @@ 40.4 +/* 40.5 + * Copyright 2021 Mike Becker. All rights reserved. 40.6 + * 40.7 + * Redistribution and use in source and binary forms, with or without 40.8 + * modification, are permitted provided that the following conditions are met: 40.9 + * 40.10 + * 1. Redistributions of source code must retain the above copyright 40.11 + * notice, this list of conditions and the following disclaimer. 40.12 + * 40.13 + * 2. Redistributions in binary form must reproduce the above copyright 40.14 + * notice, this list of conditions and the following disclaimer in the 40.15 + * documentation and/or other materials provided with the distribution. 40.16 + * 40.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 40.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 40.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 40.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 40.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 40.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 40.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 40.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 40.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40.27 + */ 40.28 + 40.29 +package de.uapcore.lightpit.servlet 40.30 + 40.31 +import de.uapcore.lightpit.AbstractServlet 40.32 +import de.uapcore.lightpit.Constants 40.33 +import de.uapcore.lightpit.HttpRequest 40.34 +import de.uapcore.lightpit.dao.DataAccessObject 40.35 +import de.uapcore.lightpit.viewmodel.LanguageView 40.36 +import java.util.* 40.37 +import javax.servlet.annotation.WebServlet 40.38 + 40.39 +@WebServlet(urlPatterns = ["/language/*"]) 40.40 +class LanguageServlet : AbstractServlet() { 40.41 + 40.42 + init { 40.43 + get("/", this::viewLanguages) 40.44 + post("/", this::selectLanguage) 40.45 + } 40.46 + 40.47 + private fun viewLanguages(http: HttpRequest, dao: DataAccessObject) { 40.48 + with(http) { 40.49 + view = LanguageView( 40.50 + availableLanguages(), 40.51 + session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale, 40.52 + request.locale 40.53 + ) 40.54 + styleSheets = listOf("language") 40.55 + render("language") 40.56 + } 40.57 + } 40.58 + 40.59 + private fun selectLanguage(http: HttpRequest, dao: DataAccessObject) { 40.60 + val lang = http.param("language") 40.61 + if (lang != null) { 40.62 + val locale = Locale.forLanguageTag(lang) 40.63 + if (!locale.language.isNullOrBlank()) { 40.64 + http.response.locale = locale 40.65 + http.session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, locale) 40.66 + } 40.67 + } 40.68 + 40.69 + viewLanguages(http, dao) 40.70 + } 40.71 + 40.72 +} 40.73 \ No newline at end of file
41.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 41.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Fri Apr 02 11:59:14 2021 +0200 41.3 @@ -0,0 +1,522 @@ 41.4 +/* 41.5 + * Copyright 2021 Mike Becker. All rights reserved. 41.6 + * 41.7 + * Redistribution and use in source and binary forms, with or without 41.8 + * modification, are permitted provided that the following conditions are met: 41.9 + * 41.10 + * 1. Redistributions of source code must retain the above copyright 41.11 + * notice, this list of conditions and the following disclaimer. 41.12 + * 41.13 + * 2. Redistributions in binary form must reproduce the above copyright 41.14 + * notice, this list of conditions and the following disclaimer in the 41.15 + * documentation and/or other materials provided with the distribution. 41.16 + * 41.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 41.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 41.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 41.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 41.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 41.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 41.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 41.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 41.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41.27 + */ 41.28 + 41.29 +package de.uapcore.lightpit.servlet 41.30 + 41.31 +import de.uapcore.lightpit.AbstractServlet 41.32 +import de.uapcore.lightpit.HttpRequest 41.33 +import de.uapcore.lightpit.dao.DataAccessObject 41.34 +import de.uapcore.lightpit.entities.* 41.35 +import de.uapcore.lightpit.types.IssueCategory 41.36 +import de.uapcore.lightpit.types.IssueStatus 41.37 +import de.uapcore.lightpit.types.VersionStatus 41.38 +import de.uapcore.lightpit.types.WebColor 41.39 +import de.uapcore.lightpit.util.AllFilter 41.40 +import de.uapcore.lightpit.util.IssueFilter 41.41 +import de.uapcore.lightpit.util.SpecificFilter 41.42 +import de.uapcore.lightpit.viewmodel.* 41.43 +import java.sql.Date 41.44 +import javax.servlet.annotation.WebServlet 41.45 + 41.46 +@WebServlet(urlPatterns = ["/projects/*"]) 41.47 +class ProjectServlet : AbstractServlet() { 41.48 + 41.49 + init { 41.50 + get("/", this::projects) 41.51 + get("/%project", this::project) 41.52 + get("/%project/issues/%version/%component/", this::project) 41.53 + get("/%project/edit", this::projectForm) 41.54 + get("/-/create", this::projectForm) 41.55 + post("/-/commit", this::projectCommit) 41.56 + 41.57 + get("/%project/versions/", this::versions) 41.58 + get("/%project/versions/%version/edit", this::versionForm) 41.59 + get("/%project/versions/-/create", this::versionForm) 41.60 + post("/%project/versions/-/commit", this::versionCommit) 41.61 + 41.62 + get("/%project/components/", this::components) 41.63 + get("/%project/components/%component/edit", this::componentForm) 41.64 + get("/%project/components/-/create", this::componentForm) 41.65 + post("/%project/components/-/commit", this::componentCommit) 41.66 + 41.67 + get("/%project/issues/%version/%component/%issue", this::issue) 41.68 + get("/%project/issues/%version/%component/%issue/edit", this::issueForm) 41.69 + get("/%project/issues/%version/%component/%issue/comment", this::issueComment) 41.70 + get("/%project/issues/%version/%component/-/create", this::issueForm) 41.71 + get("/%project/issues/%version/%component/-/commit", this::issueCommit) 41.72 + } 41.73 + 41.74 + fun projects(http: HttpRequest, dao: DataAccessObject) { 41.75 + val projects = dao.listProjects() 41.76 + val projectInfos = projects.map { 41.77 + ProjectInfo( 41.78 + project = it, 41.79 + versions = dao.listVersions(it), 41.80 + components = emptyList(), // not required in this view 41.81 + issueSummary = dao.collectIssueSummary(it) 41.82 + ) 41.83 + } 41.84 + 41.85 + with(http) { 41.86 + view = ProjectsView(projectInfos) 41.87 + navigationMenu = projectNavMenu(projects) 41.88 + styleSheets = listOf("projects") 41.89 + render("projects") 41.90 + } 41.91 + } 41.92 + 41.93 + private fun activeProjectNavMenu( 41.94 + projects: List<Project>, 41.95 + projectInfo: ProjectInfo, 41.96 + selectedVersion: Version? = null, 41.97 + selectedComponent: Component? = null 41.98 + ) = 41.99 + projectNavMenu( 41.100 + projects, 41.101 + projectInfo.versions, 41.102 + projectInfo.components, 41.103 + projectInfo.project, 41.104 + selectedVersion, 41.105 + selectedComponent 41.106 + ) 41.107 + 41.108 + sealed class LookupResult<T> { 41.109 + class NotFound<T> : LookupResult<T>() 41.110 + data class Found<T>(val elem: T?) : LookupResult<T>() 41.111 + } 41.112 + 41.113 + private fun <T : HasNode> HttpRequest.lookupPathParam(paramName: String, list: List<T>): LookupResult<T> { 41.114 + val node = pathParams[paramName] 41.115 + return if (node == null || node == "-") { 41.116 + LookupResult.Found(null) 41.117 + } else { 41.118 + val result = list.find { it.node == node } 41.119 + if (result == null) { 41.120 + LookupResult.NotFound() 41.121 + } else { 41.122 + LookupResult.Found(result) 41.123 + } 41.124 + } 41.125 + } 41.126 + 41.127 + private fun obtainProjectInfo(http: HttpRequest, dao: DataAccessObject): ProjectInfo? { 41.128 + val project = dao.findProjectByNode(http.pathParams["project"] ?: "") ?: return null 41.129 + 41.130 + val versions: List<Version> = dao.listVersions(project) 41.131 + val components: List<Component> = dao.listComponents(project) 41.132 + 41.133 + return ProjectInfo( 41.134 + project, 41.135 + versions, 41.136 + components, 41.137 + dao.collectIssueSummary(project) 41.138 + ) 41.139 + } 41.140 + 41.141 + private fun sanitizeNode(name: String): String { 41.142 + val san = name.replace(Regex("[/\\\\]"), "-") 41.143 + return if (san.startsWith(".")) { 41.144 + "v$san" 41.145 + } else { 41.146 + san 41.147 + } 41.148 + } 41.149 + 41.150 + data class PathInfos( 41.151 + val projectInfo: ProjectInfo, 41.152 + val version: Version?, 41.153 + val component: Component? 41.154 + ) { 41.155 + val issuesHref by lazyOf("projects/${projectInfo.project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/") 41.156 + } 41.157 + 41.158 + private fun withPathInfo(http: HttpRequest, dao: DataAccessObject): PathInfos? { 41.159 + val projectInfo = obtainProjectInfo(http, dao) 41.160 + if (projectInfo == null) { 41.161 + http.response.sendError(404) 41.162 + return null 41.163 + } 41.164 + 41.165 + val version = when (val result = http.lookupPathParam("version", projectInfo.versions)) { 41.166 + is LookupResult.NotFound -> { 41.167 + http.response.sendError(404) 41.168 + return null 41.169 + } 41.170 + is LookupResult.Found -> { 41.171 + result.elem 41.172 + } 41.173 + } 41.174 + val component = when (val result = http.lookupPathParam("component", projectInfo.components)) { 41.175 + is LookupResult.NotFound -> { 41.176 + http.response.sendError(404) 41.177 + return null 41.178 + } 41.179 + is LookupResult.Found -> { 41.180 + result.elem 41.181 + } 41.182 + } 41.183 + 41.184 + return PathInfos(projectInfo, version, component) 41.185 + } 41.186 + 41.187 + fun project(http: HttpRequest, dao: DataAccessObject) { 41.188 + withPathInfo(http, dao)?.run { 41.189 + val issues = dao.listIssues(IssueFilter( 41.190 + project = SpecificFilter(projectInfo.project), 41.191 + version = version?.let { SpecificFilter(it) } ?: AllFilter(), 41.192 + component = component?.let { SpecificFilter(it) } ?: AllFilter() 41.193 + )) 41.194 + 41.195 + with(http) { 41.196 + view = ProjectDetails(projectInfo, issues, version, component) 41.197 + navigationMenu = activeProjectNavMenu( 41.198 + dao.listProjects(), 41.199 + projectInfo, 41.200 + version, 41.201 + component 41.202 + ) 41.203 + styleSheets = listOf("projects") 41.204 + render("project-details") 41.205 + } 41.206 + } 41.207 + } 41.208 + 41.209 + fun projectForm(http: HttpRequest, dao: DataAccessObject) { 41.210 + val projectInfo = obtainProjectInfo(http, dao) 41.211 + if (projectInfo == null) { 41.212 + http.response.sendError(404) 41.213 + return 41.214 + } 41.215 + 41.216 + with(http) { 41.217 + view = ProjectEditView(projectInfo.project, dao.listUsers()) 41.218 + navigationMenu = activeProjectNavMenu( 41.219 + dao.listProjects(), 41.220 + projectInfo 41.221 + ) 41.222 + styleSheets = listOf("projects") 41.223 + render("project-form") 41.224 + } 41.225 + } 41.226 + 41.227 + fun projectCommit(http: HttpRequest, dao: DataAccessObject) { 41.228 + // TODO: replace defaults with throwing validator exceptions 41.229 + val project = Project(http.param("id")?.toIntOrNull() ?: -1).apply { 41.230 + name = http.param("name") ?: "" 41.231 + node = http.param("node") ?: "" 41.232 + description = http.param("description") ?: "" 41.233 + ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 41.234 + repoUrl = http.param("repoUrl") ?: "" 41.235 + owner = (http.param("owner")?.toIntOrNull() ?: -1).let { 41.236 + if (it < 0) null else dao.findUser(it) 41.237 + } 41.238 + // intentional defaults 41.239 + if (node.isBlank()) node = name 41.240 + // sanitizing 41.241 + node = sanitizeNode(node) 41.242 + } 41.243 + 41.244 + if (project.id < 0) { 41.245 + dao.insertProject(project) 41.246 + } else { 41.247 + dao.updateProject(project) 41.248 + } 41.249 + 41.250 + http.renderCommit("projects/${project.node}") 41.251 + } 41.252 + 41.253 + fun versions(http: HttpRequest, dao: DataAccessObject) { 41.254 + val projectInfo = obtainProjectInfo(http, dao) 41.255 + if (projectInfo == null) { 41.256 + http.response.sendError(404) 41.257 + return 41.258 + } 41.259 + 41.260 + with(http) { 41.261 + view = VersionsView( 41.262 + projectInfo, 41.263 + dao.listVersionSummaries(projectInfo.project) 41.264 + ) 41.265 + navigationMenu = activeProjectNavMenu( 41.266 + dao.listProjects(), 41.267 + projectInfo 41.268 + ) 41.269 + styleSheets = listOf("projects") 41.270 + render("versions") 41.271 + } 41.272 + } 41.273 + 41.274 + fun versionForm(http: HttpRequest, dao: DataAccessObject) { 41.275 + val projectInfo = obtainProjectInfo(http, dao) 41.276 + if (projectInfo == null) { 41.277 + http.response.sendError(404) 41.278 + return 41.279 + } 41.280 + 41.281 + val version: Version 41.282 + when (val result = http.lookupPathParam("version", projectInfo.versions)) { 41.283 + is LookupResult.NotFound -> { 41.284 + http.response.sendError(404) 41.285 + return 41.286 + } 41.287 + is LookupResult.Found -> { 41.288 + version = result.elem ?: Version(-1, projectInfo.project.id) 41.289 + } 41.290 + } 41.291 + 41.292 + with(http) { 41.293 + view = VersionEditView(projectInfo, version) 41.294 + navigationMenu = activeProjectNavMenu( 41.295 + dao.listProjects(), 41.296 + projectInfo, 41.297 + selectedVersion = version 41.298 + ) 41.299 + styleSheets = listOf("projects") 41.300 + render("version-form") 41.301 + } 41.302 + } 41.303 + 41.304 + fun versionCommit(http: HttpRequest, dao: DataAccessObject) { 41.305 + val id = http.param("id")?.toIntOrNull() 41.306 + val projectid = http.param("projectid")?.toIntOrNull() ?: -1 41.307 + val project = dao.findProject(projectid) 41.308 + if (id == null || project == null) { 41.309 + http.response.sendError(400) 41.310 + return 41.311 + } 41.312 + 41.313 + // TODO: replace defaults with throwing validator exceptions 41.314 + val version = Version(id, projectid).apply { 41.315 + name = http.param("name") ?: "" 41.316 + node = http.param("node") ?: "" 41.317 + ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 41.318 + status = http.param("status")?.let(VersionStatus::valueOf) ?: VersionStatus.Future 41.319 + // intentional defaults 41.320 + if (node.isBlank()) node = name 41.321 + // sanitizing 41.322 + node = sanitizeNode(node) 41.323 + } 41.324 + 41.325 + if (id < 0) { 41.326 + dao.insertVersion(version) 41.327 + } else { 41.328 + dao.updateVersion(version) 41.329 + } 41.330 + 41.331 + http.renderCommit("projects/${project.node}/versions/") 41.332 + } 41.333 + 41.334 + fun components(http: HttpRequest, dao: DataAccessObject) { 41.335 + val projectInfo = obtainProjectInfo(http, dao) 41.336 + if (projectInfo == null) { 41.337 + http.response.sendError(404) 41.338 + return 41.339 + } 41.340 + 41.341 + with(http) { 41.342 + view = ComponentsView( 41.343 + projectInfo, 41.344 + dao.listComponentSummaries(projectInfo.project) 41.345 + ) 41.346 + navigationMenu = activeProjectNavMenu( 41.347 + dao.listProjects(), 41.348 + projectInfo 41.349 + ) 41.350 + styleSheets = listOf("projects") 41.351 + render("components") 41.352 + } 41.353 + } 41.354 + 41.355 + fun componentForm(http: HttpRequest, dao: DataAccessObject) { 41.356 + val projectInfo = obtainProjectInfo(http, dao) 41.357 + if (projectInfo == null) { 41.358 + http.response.sendError(404) 41.359 + return 41.360 + } 41.361 + 41.362 + val component: Component 41.363 + when (val result = http.lookupPathParam("component", projectInfo.components)) { 41.364 + is LookupResult.NotFound -> { 41.365 + http.response.sendError(404) 41.366 + return 41.367 + } 41.368 + is LookupResult.Found -> { 41.369 + component = result.elem ?: Component(-1, projectInfo.project.id) 41.370 + } 41.371 + } 41.372 + 41.373 + with(http) { 41.374 + view = ComponentEditView(projectInfo, component, dao.listUsers()) 41.375 + navigationMenu = activeProjectNavMenu( 41.376 + dao.listProjects(), 41.377 + projectInfo, 41.378 + selectedComponent = component 41.379 + ) 41.380 + styleSheets = listOf("projects") 41.381 + render("component-form") 41.382 + } 41.383 + } 41.384 + 41.385 + fun componentCommit(http: HttpRequest, dao: DataAccessObject) { 41.386 + val id = http.param("id")?.toIntOrNull() 41.387 + val projectid = http.param("projectid")?.toIntOrNull() ?: -1 41.388 + val project = dao.findProject(projectid) 41.389 + if (id == null || project == null) { 41.390 + http.response.sendError(400) 41.391 + return 41.392 + } 41.393 + 41.394 + // TODO: replace defaults with throwing validator exceptions 41.395 + val component = Component(id, projectid).apply { 41.396 + name = http.param("name") ?: "" 41.397 + node = http.param("node") ?: "" 41.398 + ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 41.399 + color = WebColor(http.param("color") ?: "#000000") 41.400 + description = http.param("description") 41.401 + lead = (http.param("lead")?.toIntOrNull() ?: -1).let { 41.402 + if (it < 0) null else dao.findUser(it) 41.403 + } 41.404 + // intentional defaults 41.405 + if (node.isBlank()) node = name 41.406 + // sanitizing 41.407 + node = sanitizeNode(node) 41.408 + } 41.409 + 41.410 + if (id < 0) { 41.411 + dao.insertComponent(component) 41.412 + } else { 41.413 + dao.updateComponent(component) 41.414 + } 41.415 + 41.416 + http.renderCommit("projects/${project.node}/components/") 41.417 + } 41.418 + 41.419 + fun issue(http: HttpRequest, dao: DataAccessObject) { 41.420 + withPathInfo(http, dao)?.run { 41.421 + val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) 41.422 + if (issue == null) { 41.423 + http.response.sendError(404) 41.424 + return 41.425 + } 41.426 + 41.427 + val comments = dao.listComments(issue) 41.428 + 41.429 + with(http) { 41.430 + view = IssueDetailView(issue, comments, projectInfo.project, version, component) 41.431 + navigationMenu = activeProjectNavMenu( 41.432 + dao.listProjects(), 41.433 + projectInfo, 41.434 + version, 41.435 + component 41.436 + ) 41.437 + styleSheets = listOf("projects") 41.438 + render("issue-view") 41.439 + } 41.440 + } 41.441 + } 41.442 + 41.443 + fun issueForm(http: HttpRequest, dao: DataAccessObject) { 41.444 + withPathInfo(http, dao)?.run { 41.445 + val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) ?: Issue( 41.446 + -1, 41.447 + projectInfo.project, 41.448 + ) 41.449 + 41.450 + // pre-select component, if available in the path info 41.451 + issue.component = component 41.452 + 41.453 + with(http) { 41.454 + view = IssueEditView( 41.455 + issue, 41.456 + projectInfo.versions, 41.457 + projectInfo.components, 41.458 + dao.listUsers(), 41.459 + projectInfo.project, 41.460 + version, 41.461 + component 41.462 + ) 41.463 + navigationMenu = activeProjectNavMenu( 41.464 + dao.listProjects(), 41.465 + projectInfo, 41.466 + version, 41.467 + component 41.468 + ) 41.469 + styleSheets = listOf("projects") 41.470 + render("issue-form") 41.471 + } 41.472 + } 41.473 + } 41.474 + 41.475 + fun issueComment(http: HttpRequest, dao: DataAccessObject) { 41.476 + withPathInfo(http, dao)?.run { 41.477 + val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) 41.478 + if (issue == null) { 41.479 + http.response.sendError(404) 41.480 + return 41.481 + } 41.482 + 41.483 + // TODO: throw validator exception instead of using a default 41.484 + val comment = IssueComment(-1, issue.id).apply { 41.485 + author = http.remoteUser?.let { dao.findUserByName(it) } 41.486 + comment = http.param("comment") ?: "" 41.487 + } 41.488 + 41.489 + dao.insertComment(comment) 41.490 + 41.491 + http.renderCommit("${issuesHref}${issue.id}") 41.492 + } 41.493 + } 41.494 + 41.495 + fun issueCommit(http: HttpRequest, dao: DataAccessObject) { 41.496 + withPathInfo(http, dao)?.run { 41.497 + // TODO: throw validator exception instead of using defaults 41.498 + val issue = Issue( 41.499 + http.param("id")?.toIntOrNull() ?: -1, 41.500 + projectInfo.project 41.501 + ).apply { 41.502 + component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1) 41.503 + category = IssueCategory.valueOf(http.param("category") ?: "") 41.504 + status = IssueStatus.valueOf(http.param("status") ?: "") 41.505 + subject = http.param("subject") ?: "" 41.506 + description = http.param("description") ?: "" 41.507 + assignee = http.param("assignee")?.toIntOrNull()?.let { 41.508 + when (it) { 41.509 + -1 -> null 41.510 + -2 -> component?.lead 41.511 + else -> dao.findUser(it) 41.512 + } 41.513 + } 41.514 + eta = http.param("eta")?.let { Date.valueOf(it) } 41.515 + 41.516 + affectedVersions = http.paramArray("affected") 41.517 + .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } } 41.518 + resolvedVersions = http.paramArray("resolved") 41.519 + .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, projectInfo.project.id) } } 41.520 + } 41.521 + 41.522 + http.renderCommit("${issuesHref}${issue.id}") 41.523 + } 41.524 + } 41.525 +} 41.526 \ No newline at end of file
42.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 42.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/UsersServlet.kt Fri Apr 02 11:59:14 2021 +0200 42.3 @@ -0,0 +1,112 @@ 42.4 +/* 42.5 + * Copyright 2021 Mike Becker. All rights reserved. 42.6 + * 42.7 + * Redistribution and use in source and binary forms, with or without 42.8 + * modification, are permitted provided that the following conditions are met: 42.9 + * 42.10 + * 1. Redistributions of source code must retain the above copyright 42.11 + * notice, this list of conditions and the following disclaimer. 42.12 + * 42.13 + * 2. Redistributions in binary form must reproduce the above copyright 42.14 + * notice, this list of conditions and the following disclaimer in the 42.15 + * documentation and/or other materials provided with the distribution. 42.16 + * 42.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 42.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 42.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 42.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 42.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 42.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 42.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 42.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 42.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42.27 + */ 42.28 + 42.29 +package de.uapcore.lightpit.servlet 42.30 + 42.31 +import de.uapcore.lightpit.AbstractServlet 42.32 +import de.uapcore.lightpit.HttpRequest 42.33 +import de.uapcore.lightpit.LoggingTrait 42.34 +import de.uapcore.lightpit.dao.DataAccessObject 42.35 +import de.uapcore.lightpit.entities.User 42.36 +import de.uapcore.lightpit.logger 42.37 +import de.uapcore.lightpit.viewmodel.UserEditView 42.38 +import de.uapcore.lightpit.viewmodel.UsersView 42.39 +import javax.servlet.annotation.WebServlet 42.40 + 42.41 +@WebServlet(urlPatterns = ["/users/*"]) 42.42 +class UsersServlet : AbstractServlet(), LoggingTrait { 42.43 + 42.44 + init { 42.45 + get("/", this::index) 42.46 + get("/-/create", this::create) 42.47 + get("/%userid/edit", this::edit) 42.48 + post("/-/commit", this::commit) 42.49 + } 42.50 + 42.51 + private val list = "users" 42.52 + private val form = "user-form" 42.53 + 42.54 + fun index(http: HttpRequest, dao: DataAccessObject) { 42.55 + with(http) { 42.56 + view = UsersView(dao.listUsers()) 42.57 + render(list) 42.58 + } 42.59 + } 42.60 + 42.61 + fun create(http: HttpRequest, dao: DataAccessObject) { 42.62 + with(http) { 42.63 + view = UserEditView(User(-1)) 42.64 + render(form) 42.65 + } 42.66 + } 42.67 + 42.68 + fun edit(http: HttpRequest, dao: DataAccessObject) { 42.69 + val id = http.pathParams["userid"]?.toIntOrNull() 42.70 + if (id == null) { 42.71 + http.response.sendError(404) 42.72 + } else { 42.73 + val user = dao.findUser(id) 42.74 + if (user == null) { 42.75 + http.response.sendError(404) 42.76 + } else { 42.77 + with(http) { 42.78 + view = UserEditView(user) 42.79 + render(form) 42.80 + } 42.81 + } 42.82 + } 42.83 + } 42.84 + 42.85 + fun commit(http: HttpRequest, dao: DataAccessObject) { 42.86 + val id = http.param("userid")?.toIntOrNull() 42.87 + if (id == null) { 42.88 + http.response.sendError(400) 42.89 + return 42.90 + } 42.91 + 42.92 + val user = User(id) 42.93 + with(user) { 42.94 + username = http.param("username") ?: "" 42.95 + givenname = http.param("givenname") 42.96 + lastname = http.param("lastname") 42.97 + mail = http.param("mail") 42.98 + } 42.99 + 42.100 + if (dao.findUserByName(user.username) != null) { 42.101 + with(http) { 42.102 + view = UserEditView(user).apply { errorText = "validation.username.unique" } 42.103 + } 42.104 + } 42.105 + 42.106 + if (user.id > 0) { 42.107 + logger().info("Update user ${user.username} with id ${user.id}.") 42.108 + dao.updateUser(user) 42.109 + } else { 42.110 + logger().info("Insert user ${user.username}.") 42.111 + dao.insertUser(user) 42.112 + } 42.113 + http.renderCommit("users/") 42.114 + } 42.115 +} 42.116 \ No newline at end of file
43.1 --- a/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt Sat Jan 23 14:47:59 2021 +0100 43.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt Fri Apr 02 11:59:14 2021 +0200 43.3 @@ -27,7 +27,7 @@ 43.4 43.5 43.6 /** 43.7 - * Represents a web color in hexadezimal representation. 43.8 + * Represents a web color in hexadecimal representation. 43.9 * @param arg the 6 digits hex string optionally preceded by a hash symbol 43.10 */ 43.11 class WebColor(arg: String) {
44.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 44.2 +++ b/src/main/kotlin/de/uapcore/lightpit/util/Filter.kt Fri Apr 02 11:59:14 2021 +0200 44.3 @@ -0,0 +1,32 @@ 44.4 +/* 44.5 + * Copyright 2021 Mike Becker. All rights reserved. 44.6 + * 44.7 + * Redistribution and use in source and binary forms, with or without 44.8 + * modification, are permitted provided that the following conditions are met: 44.9 + * 44.10 + * 1. Redistributions of source code must retain the above copyright 44.11 + * notice, this list of conditions and the following disclaimer. 44.12 + * 44.13 + * 2. Redistributions in binary form must reproduce the above copyright 44.14 + * notice, this list of conditions and the following disclaimer in the 44.15 + * documentation and/or other materials provided with the distribution. 44.16 + * 44.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 44.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 44.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 44.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 44.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 44.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 44.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 44.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 44.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44.27 + */ 44.28 + 44.29 +package de.uapcore.lightpit.util 44.30 + 44.31 +sealed class Filter<T> 44.32 +class AllFilter<T> : Filter<T>() 44.33 +class NoneFilter<T> : Filter<T>() 44.34 +data class SpecificFilter<T>(val obj: T) : Filter<T>() 44.35 +data class RangeFilter<T>(val lower: T, val upper: T) : Filter<T>() where T : Comparable<T>
45.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 45.2 +++ b/src/main/kotlin/de/uapcore/lightpit/util/Issues.kt Fri Apr 02 11:59:14 2021 +0200 45.3 @@ -0,0 +1,71 @@ 45.4 +/* 45.5 + * Copyright 2021 Mike Becker. All rights reserved. 45.6 + * 45.7 + * Redistribution and use in source and binary forms, with or without 45.8 + * modification, are permitted provided that the following conditions are met: 45.9 + * 45.10 + * 1. Redistributions of source code must retain the above copyright 45.11 + * notice, this list of conditions and the following disclaimer. 45.12 + * 45.13 + * 2. Redistributions in binary form must reproduce the above copyright 45.14 + * notice, this list of conditions and the following disclaimer in the 45.15 + * documentation and/or other materials provided with the distribution. 45.16 + * 45.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 45.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 45.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 45.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 45.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 45.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 45.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 45.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 45.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45.27 + */ 45.28 + 45.29 +package de.uapcore.lightpit.util 45.30 + 45.31 +import de.uapcore.lightpit.entities.Component 45.32 +import de.uapcore.lightpit.entities.Issue 45.33 +import de.uapcore.lightpit.entities.Project 45.34 +import de.uapcore.lightpit.entities.Version 45.35 +import de.uapcore.lightpit.types.IssueStatusPhase 45.36 + 45.37 +data class IssueFilter( 45.38 + val project: Filter<Project> = AllFilter(), 45.39 + val version: Filter<Version> = AllFilter(), 45.40 + val component: Filter<Component> = AllFilter() 45.41 +) 45.42 + 45.43 +data class IssueSorter(val criteria: List<Criteria>) : Comparator<Issue> { 45.44 + enum class Field { 45.45 + DONE, ETA, UPDATED 45.46 + } 45.47 + 45.48 + data class Criteria(val field: Field, val asc: Boolean) 45.49 + 45.50 + override fun compare(left: Issue, right: Issue): Int { 45.51 + if (left == right) { 45.52 + return 0; 45.53 + } 45.54 + for (c in criteria) { 45.55 + val result = when (c.field) { 45.56 + Field.DONE -> (left.status.phase == IssueStatusPhase.Done).compareTo(right.status.phase == IssueStatusPhase.Done) 45.57 + Field.ETA -> { 45.58 + val l = left.eta 45.59 + val r = right.eta 45.60 + if (l == null && r == null) 0 45.61 + else if (l == null) 1 45.62 + else if (r == null) -1 45.63 + else l.compareTo(r) 45.64 + } 45.65 + Field.UPDATED -> left.updated.compareTo(right.updated) 45.66 + } 45.67 + if (result != 0) { 45.68 + return if (c.asc) result else -result 45.69 + } 45.70 + } 45.71 + return 0 45.72 + } 45.73 +} 45.74 +
46.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 46.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Components.kt Fri Apr 02 11:59:14 2021 +0200 46.3 @@ -0,0 +1,46 @@ 46.4 +/* 46.5 + * Copyright 2021 Mike Becker. All rights reserved. 46.6 + * 46.7 + * Redistribution and use in source and binary forms, with or without 46.8 + * modification, are permitted provided that the following conditions are met: 46.9 + * 46.10 + * 1. Redistributions of source code must retain the above copyright 46.11 + * notice, this list of conditions and the following disclaimer. 46.12 + * 46.13 + * 2. Redistributions in binary form must reproduce the above copyright 46.14 + * notice, this list of conditions and the following disclaimer in the 46.15 + * documentation and/or other materials provided with the distribution. 46.16 + * 46.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 46.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 46.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 46.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 46.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 46.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 46.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 46.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 46.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46.27 + */ 46.28 + 46.29 +package de.uapcore.lightpit.viewmodel 46.30 + 46.31 +import de.uapcore.lightpit.entities.Component 46.32 +import de.uapcore.lightpit.entities.User 46.33 + 46.34 +class ComponentSummary( 46.35 + val component: Component, 46.36 +) { 46.37 + val issueSummary = IssueSummary() 46.38 +} 46.39 + 46.40 +class ComponentsView( 46.41 + val projectInfo: ProjectInfo, 46.42 + val componentInfos: List<ComponentSummary> 46.43 +) : View() 46.44 + 46.45 +class ComponentEditView( 46.46 + val projectInfo: ProjectInfo, 46.47 + val component: Component, 46.48 + val users: List<User> 46.49 +) : EditView()
47.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 47.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Fri Apr 02 11:59:14 2021 +0200 47.3 @@ -0,0 +1,118 @@ 47.4 +/* 47.5 + * Copyright 2021 Mike Becker. All rights reserved. 47.6 + * 47.7 + * Redistribution and use in source and binary forms, with or without 47.8 + * modification, are permitted provided that the following conditions are met: 47.9 + * 47.10 + * 1. Redistributions of source code must retain the above copyright 47.11 + * notice, this list of conditions and the following disclaimer. 47.12 + * 47.13 + * 2. Redistributions in binary form must reproduce the above copyright 47.14 + * notice, this list of conditions and the following disclaimer in the 47.15 + * documentation and/or other materials provided with the distribution. 47.16 + * 47.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 47.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 47.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 47.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 47.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 47.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 47.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 47.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 47.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47.27 + */ 47.28 + 47.29 +package de.uapcore.lightpit.viewmodel 47.30 + 47.31 +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension 47.32 +import com.vladsch.flexmark.ext.tables.TablesExtension 47.33 +import com.vladsch.flexmark.html.HtmlRenderer 47.34 +import com.vladsch.flexmark.parser.Parser 47.35 +import com.vladsch.flexmark.util.data.MutableDataSet 47.36 +import de.uapcore.lightpit.entities.* 47.37 +import de.uapcore.lightpit.types.IssueCategory 47.38 +import de.uapcore.lightpit.types.IssueStatus 47.39 +import de.uapcore.lightpit.types.IssueStatusPhase 47.40 +import de.uapcore.lightpit.types.VersionStatus 47.41 +import kotlin.math.roundToInt 47.42 + 47.43 +class IssueSummary { 47.44 + var open = 0 47.45 + var active = 0 47.46 + var done = 0 47.47 + 47.48 + val total get() = open + active + done 47.49 + 47.50 + val openPercent get() = 100 - activePercent - donePercent 47.51 + val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0 47.52 + val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100 47.53 + 47.54 + /** 47.55 + * Adds the specified issue to the summary by incrementing the respective counter. 47.56 + * @param issue the issue 47.57 + */ 47.58 + fun add(issue: Issue) { 47.59 + when (issue.status.phase) { 47.60 + IssueStatusPhase.Open -> open++ 47.61 + IssueStatusPhase.WorkInProgress -> active++ 47.62 + IssueStatusPhase.Done -> done++ 47.63 + } 47.64 + } 47.65 +} 47.66 + 47.67 +class IssueDetailView( 47.68 + val issue: Issue, 47.69 + val comments: List<IssueComment>, 47.70 + val project: Project, 47.71 + val version: Version? = null, 47.72 + val component: Component? = null 47.73 +) : View() { 47.74 + 47.75 + init { 47.76 + val options = MutableDataSet() 47.77 + .set(Parser.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create())) 47.78 + val parser = Parser.builder(options).build() 47.79 + val renderer = HtmlRenderer.builder(options).build() 47.80 + val process = fun(it: String) = renderer.render(parser.parse(it)) 47.81 + 47.82 + issue.description = process(issue.description) 47.83 + for (comment in comments) { 47.84 + comment.comment = process(comment.comment) 47.85 + } 47.86 + } 47.87 +} 47.88 + 47.89 +class IssueEditView( 47.90 + val issue: Issue, 47.91 + val versions: List<Version>, 47.92 + val components: List<Component>, 47.93 + val users: List<User>, 47.94 + val project: Project, // TODO: allow null values to create issues from the IssuesServlet 47.95 + val version: Version? = null, 47.96 + val component: Component? = null 47.97 +) : EditView() { 47.98 + 47.99 + val versionsUpcoming: List<Version> 47.100 + val versionsRecent: List<Version> 47.101 + 47.102 + val issueStatus = IssueStatus.values() 47.103 + val issueCategory = IssueCategory.values() 47.104 + 47.105 + init { 47.106 + val recent = mutableListOf<Version>() 47.107 + val upcoming = mutableListOf<Version>() 47.108 + recent.addAll(issue.affectedVersions) 47.109 + upcoming.addAll(issue.resolvedVersions) 47.110 + for (v in versions) { 47.111 + if (v.status.isReleased) { 47.112 + if (v.status != VersionStatus.Deprecated) recent.add(v) 47.113 + } else { 47.114 + upcoming.add(v) 47.115 + } 47.116 + } 47.117 + versionsRecent = recent 47.118 + versionsUpcoming = upcoming 47.119 + } 47.120 +} 47.121 +
48.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 48.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/LanguageView.kt Fri Apr 02 11:59:14 2021 +0200 48.3 @@ -0,0 +1,34 @@ 48.4 +/* 48.5 + * Copyright 2021 Mike Becker. All rights reserved. 48.6 + * 48.7 + * Redistribution and use in source and binary forms, with or without 48.8 + * modification, are permitted provided that the following conditions are met: 48.9 + * 48.10 + * 1. Redistributions of source code must retain the above copyright 48.11 + * notice, this list of conditions and the following disclaimer. 48.12 + * 48.13 + * 2. Redistributions in binary form must reproduce the above copyright 48.14 + * notice, this list of conditions and the following disclaimer in the 48.15 + * documentation and/or other materials provided with the distribution. 48.16 + * 48.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 48.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 48.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 48.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 48.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 48.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 48.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 48.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 48.27 + */ 48.28 + 48.29 +package de.uapcore.lightpit.viewmodel 48.30 + 48.31 +import java.util.* 48.32 + 48.33 +class LanguageView( 48.34 + val languages: List<Locale>, 48.35 + val currentLanguage: Locale, 48.36 + val browserLanguage: Locale 48.37 +) : View() 48.38 \ No newline at end of file
49.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 49.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/NavMenus.kt Fri Apr 02 11:59:14 2021 +0200 49.3 @@ -0,0 +1,128 @@ 49.4 +/* 49.5 + * Copyright 2021 Mike Becker. All rights reserved. 49.6 + * 49.7 + * Redistribution and use in source and binary forms, with or without 49.8 + * modification, are permitted provided that the following conditions are met: 49.9 + * 49.10 + * 1. Redistributions of source code must retain the above copyright 49.11 + * notice, this list of conditions and the following disclaimer. 49.12 + * 49.13 + * 2. Redistributions in binary form must reproduce the above copyright 49.14 + * notice, this list of conditions and the following disclaimer in the 49.15 + * documentation and/or other materials provided with the distribution. 49.16 + * 49.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 49.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 49.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 49.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 49.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 49.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 49.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 49.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 49.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 49.27 + */ 49.28 + 49.29 +package de.uapcore.lightpit.viewmodel 49.30 + 49.31 +import de.uapcore.lightpit.entities.Component 49.32 +import de.uapcore.lightpit.entities.Project 49.33 +import de.uapcore.lightpit.entities.Version 49.34 + 49.35 +class NavMenuEntry( 49.36 + val level: Int, 49.37 + val caption: String, 49.38 + val href: String, 49.39 + val title: String = "", 49.40 + val active: Boolean = false, 49.41 + val resolveCaption: Boolean = false, 49.42 + val iconColor: String? = null 49.43 +) { 49.44 + val iconUseCssClass = iconColor != null && !iconColor.startsWith("#") 49.45 +} 49.46 + 49.47 +class NavMenu(val entries: List<NavMenuEntry>) 49.48 + 49.49 +fun projectNavMenu( 49.50 + projects: List<Project>, 49.51 + versions: List<Version> = emptyList(), 49.52 + components: List<Component> = emptyList(), 49.53 + selectedProject: Project? = null, 49.54 + selectedVersion: Version? = null, 49.55 + selectedComponent: Component? = null 49.56 +) = NavMenu( 49.57 + sequence { 49.58 + val cnode = selectedComponent?.node ?: "-" 49.59 + val vnode = selectedVersion?.node ?: "-" 49.60 + for (project in projects) { 49.61 + val active = project == selectedProject 49.62 + yield( 49.63 + NavMenuEntry( 49.64 + level = 0, 49.65 + caption = project.name, 49.66 + href = "projects/${project.node}", 49.67 + active = active 49.68 + ) 49.69 + ) 49.70 + if (active) { 49.71 + yield( 49.72 + NavMenuEntry( 49.73 + level = 1, 49.74 + caption = "navmenu.versions", 49.75 + resolveCaption = true, 49.76 + href = "projects/${project.node}/versions/" 49.77 + ) 49.78 + ) 49.79 + yield( 49.80 + NavMenuEntry( 49.81 + level = 2, 49.82 + caption = "navmenu.all", 49.83 + resolveCaption = true, 49.84 + href = "projects/${project.node}/issues/-/${cnode}/", 49.85 + iconColor = "#000000" 49.86 + ) 49.87 + ) 49.88 + for (version in versions) { 49.89 + yield( 49.90 + NavMenuEntry( 49.91 + level = 2, 49.92 + caption = version.name, 49.93 + title = "version.status.${version.status}", 49.94 + href = "projects/${project.node}/issues/${version.node}/${cnode}/", 49.95 + iconColor = "version-${version.status}", 49.96 + active = version == selectedVersion 49.97 + ) 49.98 + ) 49.99 + } 49.100 + yield( 49.101 + NavMenuEntry( 49.102 + level = 1, 49.103 + caption = "navmenu.components", 49.104 + resolveCaption = true, 49.105 + href = "projects/${project.node}/components/" 49.106 + ) 49.107 + ) 49.108 + yield( 49.109 + NavMenuEntry( 49.110 + level = 2, 49.111 + caption = "navmenu.all", 49.112 + resolveCaption = true, 49.113 + href = "projects/${project.node}/issues/${vnode}/-/", 49.114 + iconColor = "#000000" 49.115 + ) 49.116 + ) 49.117 + for (component in components) { 49.118 + yield( 49.119 + NavMenuEntry( 49.120 + level = 2, 49.121 + caption = component.name, 49.122 + href = "projects/${project.node}/issues/${vnode}/${component.node}/", 49.123 + iconColor = "${component.color}", 49.124 + active = component == selectedComponent 49.125 + ) 49.126 + ) 49.127 + } 49.128 + } 49.129 + } 49.130 + }.toList() 49.131 +)
50.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 50.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt Fri Apr 02 11:59:14 2021 +0200 50.3 @@ -0,0 +1,66 @@ 50.4 +/* 50.5 + * Copyright 2021 Mike Becker. All rights reserved. 50.6 + * 50.7 + * Redistribution and use in source and binary forms, with or without 50.8 + * modification, are permitted provided that the following conditions are met: 50.9 + * 50.10 + * 1. Redistributions of source code must retain the above copyright 50.11 + * notice, this list of conditions and the following disclaimer. 50.12 + * 50.13 + * 2. Redistributions in binary form must reproduce the above copyright 50.14 + * notice, this list of conditions and the following disclaimer in the 50.15 + * documentation and/or other materials provided with the distribution. 50.16 + * 50.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 50.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 50.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 50.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 50.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 50.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 50.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 50.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50.27 + */ 50.28 + 50.29 +package de.uapcore.lightpit.viewmodel 50.30 + 50.31 +import de.uapcore.lightpit.entities.* 50.32 +import de.uapcore.lightpit.types.VersionStatus 50.33 + 50.34 +class ProjectInfo( 50.35 + val project: Project, 50.36 + /** 50.37 + * List of versions, sorted by status descending. 50.38 + */ 50.39 + var versions: List<Version>, 50.40 + var components: List<Component>, 50.41 + var issueSummary: IssueSummary 50.42 +) { 50.43 + val latestVersion = versions.firstOrNull { it.status == VersionStatus.Released } 50.44 + val nextVersion = versions.findLast { !it.status.isReleased } 50.45 +} 50.46 + 50.47 +class ProjectsView( 50.48 + val projects: List<ProjectInfo> 50.49 +) : View() 50.50 + 50.51 +class ProjectDetails( 50.52 + val projectInfo: ProjectInfo, 50.53 + val issues: List<Issue>, 50.54 + val version: Version? = null, 50.55 + val component: Component? = null 50.56 +) : View() { 50.57 + val issueSummary = IssueSummary() 50.58 + val versionInfo: VersionInfo? 50.59 + 50.60 + init { 50.61 + issues.forEach(issueSummary::add) 50.62 + versionInfo = version?.let { VersionInfo(it, issues) } 50.63 + } 50.64 +} 50.65 + 50.66 +class ProjectEditView( 50.67 + val project: Project, 50.68 + val users: List<User> 50.69 +) : EditView()
51.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 51.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Users.kt Fri Apr 02 11:59:14 2021 +0200 51.3 @@ -0,0 +1,36 @@ 51.4 +/* 51.5 + * Copyright 2021 Mike Becker. All rights reserved. 51.6 + * 51.7 + * Redistribution and use in source and binary forms, with or without 51.8 + * modification, are permitted provided that the following conditions are met: 51.9 + * 51.10 + * 1. Redistributions of source code must retain the above copyright 51.11 + * notice, this list of conditions and the following disclaimer. 51.12 + * 51.13 + * 2. Redistributions in binary form must reproduce the above copyright 51.14 + * notice, this list of conditions and the following disclaimer in the 51.15 + * documentation and/or other materials provided with the distribution. 51.16 + * 51.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 51.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 51.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 51.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 51.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 51.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 51.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 51.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 51.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51.27 + */ 51.28 + 51.29 +package de.uapcore.lightpit.viewmodel 51.30 + 51.31 +import de.uapcore.lightpit.entities.User 51.32 + 51.33 +class UsersView( 51.34 + val users: List<User> 51.35 +) : View() 51.36 + 51.37 +class UserEditView( 51.38 + val user: User 51.39 +) : EditView()
52.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 52.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Versions.kt Fri Apr 02 11:59:14 2021 +0200 52.3 @@ -0,0 +1,76 @@ 52.4 +/* 52.5 + * Copyright 2021 Mike Becker. All rights reserved. 52.6 + * 52.7 + * Redistribution and use in source and binary forms, with or without 52.8 + * modification, are permitted provided that the following conditions are met: 52.9 + * 52.10 + * 1. Redistributions of source code must retain the above copyright 52.11 + * notice, this list of conditions and the following disclaimer. 52.12 + * 52.13 + * 2. Redistributions in binary form must reproduce the above copyright 52.14 + * notice, this list of conditions and the following disclaimer in the 52.15 + * documentation and/or other materials provided with the distribution. 52.16 + * 52.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 52.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 52.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 52.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 52.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 52.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 52.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 52.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 52.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 52.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 52.27 + */ 52.28 + 52.29 +package de.uapcore.lightpit.viewmodel 52.30 + 52.31 +import de.uapcore.lightpit.entities.Issue 52.32 +import de.uapcore.lightpit.entities.Version 52.33 +import de.uapcore.lightpit.types.VersionStatus 52.34 + 52.35 +class VersionInfo( 52.36 + val version: Version, 52.37 + val issues: List<Issue> 52.38 +) { 52.39 + val reportedTotal = IssueSummary() 52.40 + val resolvedTotal = IssueSummary() 52.41 + val reported: List<Issue> 52.42 + val resolved: List<Issue> 52.43 + 52.44 + init { 52.45 + val reported = mutableListOf<Issue>() 52.46 + val resolved = mutableListOf<Issue>() 52.47 + for (issue in issues) { 52.48 + if (issue.affectedVersions.contains(version)) { 52.49 + reportedTotal.add(issue) 52.50 + reported.add(issue) 52.51 + } 52.52 + if (issue.resolvedVersions.contains(version)) { 52.53 + resolvedTotal.add(issue) 52.54 + resolved.add(issue) 52.55 + } 52.56 + } 52.57 + this.reported = reported 52.58 + this.resolved = resolved 52.59 + } 52.60 +} 52.61 + 52.62 +class VersionSummary( 52.63 + val version: Version 52.64 +) { 52.65 + val reportedTotal = IssueSummary() 52.66 + val resolvedTotal = IssueSummary() 52.67 +} 52.68 + 52.69 +class VersionsView( 52.70 + val projectInfo: ProjectInfo, 52.71 + val versionInfos: List<VersionSummary> 52.72 +) : View() 52.73 + 52.74 +class VersionEditView( 52.75 + val projectInfo: ProjectInfo, 52.76 + val version: Version 52.77 +) : EditView() { 52.78 + val versionStatus = VersionStatus.values() 52.79 +}
53.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 53.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/View.kt Fri Apr 02 11:59:14 2021 +0200 53.3 @@ -0,0 +1,31 @@ 53.4 +/* 53.5 + * Copyright 2021 Mike Becker. All rights reserved. 53.6 + * 53.7 + * Redistribution and use in source and binary forms, with or without 53.8 + * modification, are permitted provided that the following conditions are met: 53.9 + * 53.10 + * 1. Redistributions of source code must retain the above copyright 53.11 + * notice, this list of conditions and the following disclaimer. 53.12 + * 53.13 + * 2. Redistributions in binary form must reproduce the above copyright 53.14 + * notice, this list of conditions and the following disclaimer in the 53.15 + * documentation and/or other materials provided with the distribution. 53.16 + * 53.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 53.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 53.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 53.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 53.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 53.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 53.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 53.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 53.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53.27 + */ 53.28 + 53.29 +package de.uapcore.lightpit.viewmodel 53.30 + 53.31 +abstract class View 53.32 +abstract class EditView : View() { 53.33 + var errorText: String? = null 53.34 +}
54.1 --- a/src/main/resources/localization/strings.properties Sat Jan 23 14:47:59 2021 +0100 54.2 +++ b/src/main/resources/localization/strings.properties Fri Apr 02 11:59:14 2021 +0200 54.3 @@ -33,6 +33,7 @@ 54.4 button.language.submit = Switch language 54.5 button.okay=OK 54.6 button.project.create=New Project 54.7 +button.project.edit=Edit Project 54.8 button.user.create=Add Developer 54.9 button.version.create=New Version 54.10 button.version.edit=Edit Version 54.11 @@ -80,7 +81,6 @@ 54.12 issue.status=Status 54.13 issue.subject=Subject 54.14 issue.updated=Updated 54.15 -issue.without-version=No Assigned Version 54.16 issues.active=In Progress 54.17 issues.done=Done 54.18 issues.open=Open 54.19 @@ -94,7 +94,6 @@ 54.20 menu.users=Developer 54.21 navmenu.all=all 54.22 navmenu.components=Components 54.23 -navmenu.unassigned=unassigned 54.24 navmenu.versions=Versions 54.25 no-projects=Welcome to LightPIT. Start off by creating a new project! 54.26 no-users=No developers have been configured yet. 54.27 @@ -118,6 +117,7 @@ 54.28 user.lastname=Last Name 54.29 user.mail=E-Mail 54.30 username=User Name 54.31 +validation.username.unique=Username is already taken. 54.32 version.latest=Latest Version 54.33 version.next=Next Version 54.34 version.status.Deprecated=Deprecated 54.35 @@ -126,4 +126,4 @@ 54.36 version.status.Released=Released 54.37 version.status.Unreleased=Unreleased 54.38 version.status=Status 54.39 -version=Version 54.40 +version=Version 54.41 \ No newline at end of file
55.1 --- a/src/main/resources/localization/strings_de.properties Sat Jan 23 14:47:59 2021 +0100 55.2 +++ b/src/main/resources/localization/strings_de.properties Fri Apr 02 11:59:14 2021 +0200 55.3 @@ -33,6 +33,7 @@ 55.4 button.language.submit = Sprache ausw\u00e4hlen 55.5 button.okay=OK 55.6 button.project.create=Neues Projekt 55.7 +button.project.edit=Projekt Bearbeiten 55.8 button.user.create=Neuer Entwickler 55.9 button.version.create=Neue Version 55.10 button.version.edit=Version Bearbeiten 55.11 @@ -80,7 +81,6 @@ 55.12 issue.status=Status 55.13 issue.subject=Thema 55.14 issue.updated=Aktualisiert 55.15 -issue.without-version=Keine Version zugeordnet 55.16 issues.active=In Arbeit 55.17 issues.done=Erledigt 55.18 issues.open=Offen 55.19 @@ -94,7 +94,6 @@ 55.20 menu.users=Entwickler 55.21 navmenu.all=Alle 55.22 navmenu.components=Komponenten 55.23 -navmenu.unassigned=Nicht Zugewiesen 55.24 navmenu.versions=Versionen 55.25 no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes! 55.26 no-users=Bislang wurden keine Entwickler hinterlegt. 55.27 @@ -118,6 +117,7 @@ 55.28 user.lastname=Nachname 55.29 user.mail=E-Mail 55.30 username=Benutzername 55.31 +validation.username.unique=Der Benutzername wird bereits verwendet. 55.32 version.latest=Neuste Version 55.33 version.next=N\u00e4chste Version 55.34 version.status.Deprecated=Veraltet
56.1 --- a/src/main/webapp/WEB-INF/jsp/component-form.jsp Sat Jan 23 14:47:59 2021 +0100 56.2 +++ b/src/main/webapp/WEB-INF/jsp/component-form.jsp Fri Apr 02 11:59:14 2021 +0200 56.3 @@ -32,7 +32,7 @@ 56.4 <c:set var="component" scope="page" value="${viewmodel.component}"/> 56.5 <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> 56.6 56.7 -<form action="./projects/commit-component" method="post"> 56.8 +<form action="./projects/${project.node}/components/-/commit" method="post"> 56.9 <table class="formtable" style="width: 70ch"> 56.10 <colgroup> 56.11 <col> 56.12 @@ -43,7 +43,7 @@ 56.13 <th><fmt:message key="project"/></th> 56.14 <td> 56.15 <c:out value="${project.name}" /> 56.16 - <input type="hidden" name="pid" value="${project.id}" /> 56.17 + <input type="hidden" name="projectid" value="${project.id}" /> 56.18 </td> 56.19 </tr> 56.20 <tr>
57.1 --- a/src/main/webapp/WEB-INF/jsp/components.jsp Sat Jan 23 14:47:59 2021 +0100 57.2 +++ b/src/main/webapp/WEB-INF/jsp/components.jsp Fri Apr 02 11:59:14 2021 +0200 57.3 @@ -35,8 +35,8 @@ 57.4 <%@include file="../jspf/project-header.jspf"%> 57.5 57.6 <div id="tool-area"> 57.7 - <a href="./projects/${project.node}/create-component" class="button"><fmt:message key="button.component.create"/></a> 57.8 - <a href="./projects/${project.node}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> 57.9 + <a href="./projects/${project.node}/components/-/create" class="button"><fmt:message key="button.component.create"/></a> 57.10 + <a href="./projects/${project.node}/issues/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> 57.11 </div> 57.12 57.13 <h2><fmt:message key="progress" /></h2> 57.14 @@ -75,7 +75,7 @@ 57.15 <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/components/${componentInfo.component.node}/edit">✎</a></td> 57.16 <td rowspan="2"> 57.17 <div class="navmenu-icon" style="background-color: ${componentInfo.component.color}"></div> 57.18 - <a href="./projects/${project.node}/${componentInfo.component.node}/all-versions/issues/"> 57.19 + <a href="./projects/${project.node}/issues/-/${componentInfo.component.node}/"> 57.20 <c:out value="${componentInfo.component.name}"/> 57.21 </a> 57.22 </td>
58.1 --- a/src/main/webapp/WEB-INF/jsp/error.jsp Sat Jan 23 14:47:59 2021 +0100 58.2 +++ b/src/main/webapp/WEB-INF/jsp/error.jsp Fri Apr 02 11:59:14 2021 +0200 58.3 @@ -26,14 +26,13 @@ 58.4 --%> 58.5 <%@page pageEncoding="UTF-8" %> 58.6 <%@page import="de.uapcore.lightpit.Constants" %> 58.7 -<%@page import="de.uapcore.lightpit.modules.ErrorModule" %> 58.8 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 58.9 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 58.10 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 58.11 58.12 <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/> 58.13 <c:set scope="page" var="errorCode" value="${requestScope['javax.servlet.error.status_code']}"/> 58.14 -<c:set scope="page" var="returnLink" value="${requestScope[ErrorModule.REQ_ATTR_RETURN_LINK]}"/> 58.15 +<c:set scope="page" var="returnLink" value="${requestScope[Constants.REQ_ATTR_REFERER]}"/> 58.16 58.17 <div id="error-page"> 58.18 <h1><fmt:message key="error.headline"/></h1>
59.1 --- a/src/main/webapp/WEB-INF/jsp/issue-form.jsp Sat Jan 23 14:47:59 2021 +0100 59.2 +++ b/src/main/webapp/WEB-INF/jsp/issue-form.jsp Fri Apr 02 11:59:14 2021 +0200 59.3 @@ -29,10 +29,15 @@ 59.4 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 59.5 59.6 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueEditView" scope="request"/> 59.7 + 59.8 <c:set var="issue" scope="page" value="${viewmodel.issue}" /> 59.9 +<c:set var="project" scope="page" value="${viewmodel.project}"/> 59.10 +<c:set var="component" scope="page" value="${viewmodel.component}"/> 59.11 +<c:set var="version" scope="page" value="${viewmodel.version}"/> 59.12 59.13 -<%-- TODO: change to ./issues/commit --%> 59.14 -<form action="./projects/commit-issue" method="post"> 59.15 +<c:set var="issuesHref" value="./projects/${project.node}/issues/${empty version ? '-' : version.node }/${empty component ? '-' : component.node}/"/> 59.16 + 59.17 +<form action="${issuesHref}-/commit-issue" method="post"> 59.18 <table class="formtable fullwidth"> 59.19 <colgroup> 59.20 <col> 59.21 @@ -49,12 +54,12 @@ 59.22 <th><fmt:message key="project"/></th> 59.23 <td> 59.24 <c:choose> 59.25 - <c:when test="${not empty issue.project}"> 59.26 + <c:when test="${issue.project.id ge 0}"> 59.27 <c:out value="${issue.project.name}" /> 59.28 - <input type="hidden" name="pid" value="${issue.project.id}" /> 59.29 + <input type="hidden" name="project" value="${issue.project.id}" /> 59.30 </c:when> 59.31 <c:otherwise> 59.32 - <select name="pid" required> 59.33 + <select name="project" required> 59.34 <c:forEach var="project" items="${viewmodel.projects}"> 59.35 <option value="${project.id}"> 59.36 <c:out value="${project.name}" /> 59.37 @@ -179,13 +184,12 @@ 59.38 <label for="create-another"><fmt:message key="button.issue.create.another"/> </label> 59.39 <input type="hidden" name="id" value="${issue.id}"/> 59.40 <c:if test="${issue.id ge 0}"> 59.41 - <a href="./projects/${issue.project.node}/issues/${issue.id}/view" class="button"> 59.42 + <a href="${issuesHref}${issue.id}" class="button"> 59.43 <fmt:message key="button.cancel"/> 59.44 </a> 59.45 </c:if> 59.46 <c:if test="${issue.id lt 0}"> 59.47 - <%-- TODO: fix #14 --%> 59.48 - <a href="./projects/${issue.project.node}/all-components/all-versions/issues/" class="button"> 59.49 + <a href="${issuesHref}" class="button"> 59.50 <fmt:message key="button.cancel"/> 59.51 </a> 59.52 </c:if>
60.1 --- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Sat Jan 23 14:47:59 2021 +0100 60.2 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Fri Apr 02 11:59:14 2021 +0200 60.3 @@ -29,8 +29,14 @@ 60.4 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 60.5 60.6 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueDetailView" scope="request"/> 60.7 + 60.8 +<c:set var="project" scope="page" value="${viewmodel.project}"/> 60.9 +<c:set var="component" scope="page" value="${viewmodel.component}"/> 60.10 +<c:set var="version" scope="page" value="${viewmodel.version}"/> 60.11 <c:set var="issue" scope="page" value="${viewmodel.issue}" /> 60.12 60.13 +<c:set var="issuesHref" scope="page" value="./projects/${project.node}/issues/${empty version ? '-' : version.node }/${empty component ? '-' : component.node}/"/> 60.14 + 60.15 <table class="formtable fullwidth"> 60.16 <colgroup> 60.17 <col> 60.18 @@ -137,11 +143,10 @@ 60.19 <tfoot> 60.20 <tr> 60.21 <td colspan="2"> 60.22 - <%-- TODO: fix #14 --%> 60.23 - <a href="./projects/${issue.project.node}/all-components/all-versions/issues/" class="button"> 60.24 + <a href="${issuesHref}" class="button"> 60.25 <fmt:message key="button.cancel"/> 60.26 </a> 60.27 - <a href="./projects/${issue.project.node}/issues/${issue.id}/edit" class="button submit"> 60.28 + <a href="${issuesHref}${issue.id}/edit" class="button submit"> 60.29 <fmt:message key="button.issue.edit"/> 60.30 </a> 60.31 </td> 60.32 @@ -152,7 +157,7 @@ 60.33 <hr class="comments-separator"/> 60.34 <h2><fmt:message key="issue.comments"/></h2> 60.35 <c:if test="${viewmodel.issue.id ge 0}"> 60.36 -<form id="comment-form" action="./projects/commit-issue-comment" method="post"> 60.37 +<form id="comment-form" action="${issuesHref}${issue.id}/comment" method="post"> 60.38 <table class="formtable fullwidth"> 60.39 <tbody> 60.40 <tr> 60.41 @@ -162,7 +167,6 @@ 60.42 <tfoot> 60.43 <tr> 60.44 <td> 60.45 - <input type="hidden" name="issueid" value="${issue.id}"/> 60.46 <button type="submit"><fmt:message key="button.comment"/></button> 60.47 </td> 60.48 </tr>
61.1 --- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Sat Jan 23 14:47:59 2021 +0100 61.2 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Fri Apr 02 11:59:14 2021 +0200 61.3 @@ -24,37 +24,28 @@ 61.4 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 61.5 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61.6 --%> 61.7 -<%@page pageEncoding="UTF-8" import="de.uapcore.lightpit.viewmodel.ProjectView" %> 61.8 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 61.9 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 61.10 <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 61.11 61.12 -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectDetailsView" scope="request" /> 61.13 +<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectDetails" scope="request" /> 61.14 61.15 <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> 61.16 -<c:set var="component" scope="page" value="${viewmodel.componentFilter}"/> 61.17 +<c:set var="component" scope="page" value="${viewmodel.component}"/> 61.18 +<c:set var="version" scope="page" value="${viewmodel.version}"/> 61.19 <%@include file="../jspf/project-header.jspf"%> 61.20 61.21 <div id="tool-area"> 61.22 - <c:remove var="createIssueParams"/> 61.23 - <c:if test="${viewmodel.versionFilter.id gt 0}"> 61.24 - <c:set var="createIssueParams">&vid=${viewmodel.versionFilter.id}</c:set> 61.25 + <a href="./projects/${project.node}/issues/${empty version ? '-' : version.node}/${empty component ? '-' : component.node}/-/create" class="button"><fmt:message key="button.issue.create"/></a> 61.26 + <a href="./projects/${project.node}/edit" class="button"><fmt:message key="button.project.edit"/></a> 61.27 + <c:if test="${not empty version}"> 61.28 + <a href="./projects/${project.node}/versions/${version.node}/edit" class="button"><fmt:message key="button.version.edit"/></a> 61.29 </c:if> 61.30 - <c:if test="${viewmodel.componentFilter.id gt 0}"> 61.31 - <c:set var="createIssueParams">${createIssueParams}&cid=${viewmodel.componentFilter.id}</c:set> 61.32 + <a href="./projects/${project.node}/versions/-/create" class="button"><fmt:message key="button.version.create"/></a> 61.33 + <c:if test="${not empty component}"> 61.34 + <a href="./projects/${project.node}/components/${component.node}/edit" class="button"><fmt:message key="button.component.edit"/></a> 61.35 </c:if> 61.36 - <c:if test="${not empty createIssueParams}"> 61.37 - <c:set var="createIssueParams">?${fn:substringAfter(createIssueParams, "&")}</c:set> 61.38 - </c:if> 61.39 - <a href="./projects/${project.node}/create-issue${createIssueParams}" class="button"><fmt:message key="button.issue.create"/></a> 61.40 - <c:if test="${viewmodel.versionFilter.id gt 0}"> 61.41 - <a href="./projects/${project.node}/versions/${viewmodel.versionFilter.node}/edit" class="button"><fmt:message key="button.version.edit"/></a> 61.42 - </c:if> 61.43 - <a href="./projects/${project.node}/create-version" class="button"><fmt:message key="button.version.create"/></a> 61.44 - <c:if test="${viewmodel.componentFilter.id gt 0}"> 61.45 - <a href="./projects/${project.node}/components/${viewmodel.componentFilter.node}/edit" class="button"><fmt:message key="button.component.edit"/></a> 61.46 - </c:if> 61.47 - <a href="./projects/${project.node}/create-component" class="button"><fmt:message key="button.component.create"/></a> 61.48 + <a href="./projects/${project.node}/components/-/create" class="button"><fmt:message key="button.component.create"/></a> 61.49 </div> 61.50 61.51 <h2><fmt:message key="progress" /></h2> 61.52 @@ -63,24 +54,19 @@ 61.53 <%@include file="../jspf/issue-summary.jspf"%> 61.54 61.55 <c:choose> 61.56 - <c:when test="${viewmodel.versionFilter eq ProjectView.NO_VERSION or viewmodel.versionFilter eq ProjectView.ALL_VERSIONS}"> 61.57 + <c:when test="${empty viewmodel.versionInfo}"> 61.58 <h2> 61.59 - <c:if test="${viewmodel.versionFilter eq ProjectView.NO_VERSION}"> 61.60 - <fmt:message key="issue.without-version" /> 61.61 - </c:if> 61.62 - <c:if test="${viewmodel.versionFilter ne ProjectView.NO_VERSION}"> 61.63 - <fmt:message key="issues" /> 61.64 - </c:if> 61.65 + <fmt:message key="issues" /> 61.66 </h2> 61.67 - <c:set var="summary" value="${viewmodel.projectDetails.issueSummary}"/> 61.68 - <c:set var="issues" value="${viewmodel.projectDetails.issues}"/> 61.69 + <c:set var="summary" value="${viewmodel.issueSummary}"/> 61.70 + <c:set var="issues" value="${viewmodel.issues}"/> 61.71 <%@include file="../jspf/issue-summary.jspf"%> 61.72 <c:if test="${not empty issues}"> 61.73 <%@include file="../jspf/issue-list.jspf"%> 61.74 </c:if> 61.75 </c:when> 61.76 <c:otherwise> 61.77 - <c:set var="versionInfo" value="${viewmodel.projectDetails.versionInfo}"/> 61.78 + <c:set var="versionInfo" value="${viewmodel.versionInfo}"/> 61.79 <h2> 61.80 <fmt:message key="version" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/> 61.81 </h2>
62.1 --- a/src/main/webapp/WEB-INF/jsp/project-form.jsp Sat Jan 23 14:47:59 2021 +0100 62.2 +++ b/src/main/webapp/WEB-INF/jsp/project-form.jsp Fri Apr 02 11:59:14 2021 +0200 62.3 @@ -31,7 +31,7 @@ 62.4 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectEditView" scope="request" /> 62.5 <c:set var="project" scope="page" value="${viewmodel.project}"/> 62.6 62.7 -<form action="./projects/commit" method="post"> 62.8 +<form action="./projects/-/commit" method="post"> 62.9 <table class="formtable"> 62.10 <colgroup> 62.11 <col> 62.12 @@ -77,7 +77,7 @@ 62.13 <tfoot> 62.14 <tr> 62.15 <td colspan="2"> 62.16 - <input type="hidden" name="pid" value="${project.id}"/> 62.17 + <input type="hidden" name="id" value="${project.id}"/> 62.18 <a href="./projects/" class="button"> 62.19 <fmt:message key="button.cancel"/> 62.20 </a>
63.1 --- a/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Sat Jan 23 14:47:59 2021 +0100 63.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 63.3 @@ -1,104 +0,0 @@ 63.4 -<%-- 63.5 -DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 63.6 - 63.7 -Copyright 2021 Mike Becker. All rights reserved. 63.8 - 63.9 -Redistribution and use in source and binary forms, with or without 63.10 -modification, are permitted provided that the following conditions are met: 63.11 - 63.12 -1. Redistributions of source code must retain the above copyright 63.13 -notice, this list of conditions and the following disclaimer. 63.14 - 63.15 -2. Redistributions in binary form must reproduce the above copyright 63.16 -notice, this list of conditions and the following disclaimer in the 63.17 -documentation and/or other materials provided with the distribution. 63.18 - 63.19 -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 63.20 -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 63.21 -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 63.22 -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 63.23 -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 63.24 -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 63.25 -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 63.26 -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 63.27 -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 63.28 -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63.29 ---%> 63.30 -<%@page pageEncoding="UTF-8" 63.31 - import="de.uapcore.lightpit.viewmodel.ProjectView" 63.32 - import="de.uapcore.lightpit.types.VersionStatus" 63.33 -%> 63.34 -<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 63.35 -<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 63.36 - 63.37 -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectView" scope="request"/> 63.38 - 63.39 -<c:forEach var="projectInfo" items="${viewmodel.projectList}"> 63.40 - <c:set var="isActive" value="${viewmodel.projectInfo.project eq projectInfo.project}" /> 63.41 - <div class="menuEntry level-0" <c:if test="${isActive}">data-active</c:if> > 63.42 - <a href="projects/${projectInfo.project.node}/versions/"> 63.43 - <c:out value="${projectInfo.project.name}"/> 63.44 - </a> 63.45 - </div> 63.46 - <c:if test="${isActive}"> 63.47 - <!-- VERSIONS --> 63.48 - <c:set var="componentNode" value="${not empty viewmodel.componentFilter ? viewmodel.componentFilter.node : 'all-components'}"/> 63.49 - <div class="menuEntry level-1" <c:if test="${viewmodel.selectedPage eq ProjectView.SELECTED_PAGE_VERSIONS}">data-active</c:if> > 63.50 - <a href="projects/${projectInfo.project.node}/versions/"> 63.51 - <fmt:message key="navmenu.versions"/> 63.52 - </a> 63.53 - </div> 63.54 - <div class="menuEntry level-2" <c:if test="${viewmodel.versionFilter eq ProjectView.ALL_VERSIONS}">data-active</c:if>> 63.55 - <div class="navmenu-icon" style="background: black"></div> 63.56 - <a href="projects/${projectInfo.project.node}/${componentNode}/all-versions/issues/"> 63.57 - <fmt:message key="navmenu.all" /> 63.58 - </a> 63.59 - </div> 63.60 - <div class="menuEntry level-2" <c:if test="${viewmodel.versionFilter eq ProjectView.NO_VERSION}">data-active</c:if>> 63.61 - <div class="navmenu-icon" style="background: black"></div> 63.62 - <a href="projects/${projectInfo.project.node}/${componentNode}/no-version/issues/"> 63.63 - <fmt:message key="navmenu.unassigned" /> 63.64 - </a> 63.65 - </div> 63.66 - <c:forEach var="version" items="${viewmodel.projectInfo.versions}"> 63.67 - <c:set var="isVersionActive" value="${viewmodel.versionFilter eq version}" /> 63.68 - <c:if test="${version.status ne VersionStatus.Deprecated or isVersionActive}"> 63.69 - <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> 63.70 - title="<fmt:message key="version.status.${version.status}" />"> 63.71 - <div class="navmenu-icon version-${version.status}"></div> 63.72 - <a href="projects/${projectInfo.project.node}/${componentNode}/${version.node}/issues/"> 63.73 - <c:out value="${version.name}"/> 63.74 - </a> 63.75 - </div> 63.76 - </c:if> 63.77 - </c:forEach> 63.78 - <!-- COMPONENTS --> 63.79 - <c:set var="versionNode" value="${not empty viewmodel.versionFilter ? viewmodel.versionFilter.node : 'all-versions'}"/> 63.80 - <div class="menuEntry level-1" <c:if test="${viewmodel.selectedPage eq ProjectView.SELECTED_PAGE_COMPONENTS}">data-active</c:if>> 63.81 - <a href="projects/${projectInfo.project.node}/components/"> 63.82 - <fmt:message key="navmenu.components"/> 63.83 - </a> 63.84 - </div> 63.85 - <div class="menuEntry level-2" <c:if test="${viewmodel.componentFilter eq ProjectView.ALL_COMPONENTS}">data-active</c:if>> 63.86 - <div class="navmenu-icon" style="background: black"></div> 63.87 - <a href="projects/${projectInfo.project.node}/all-components/${versionNode}/issues/"> 63.88 - <fmt:message key="navmenu.all" /> 63.89 - </a> 63.90 - </div> 63.91 - <div class="menuEntry level-2" <c:if test="${viewmodel.componentFilter eq ProjectView.NO_COMPONENT}">data-active</c:if>> 63.92 - <div class="navmenu-icon" style="background: black"></div> 63.93 - <a href="projects/${projectInfo.project.node}/no-component/${versionNode}/issues/"> 63.94 - <fmt:message key="navmenu.unassigned" /> 63.95 - </a> 63.96 - </div> 63.97 - <c:forEach var="component" items="${viewmodel.projectInfo.components}"> 63.98 - <c:set var="isComponentActive" value="${viewmodel.componentFilter eq component}" /> 63.99 - <div class="menuEntry level-2" <c:if test="${isComponentActive}">data-active</c:if> > 63.100 - <div class="navmenu-icon" style="background-color: ${component.color}"></div> 63.101 - <a href="projects/${projectInfo.project.node}/${component.node}/${versionNode}/issues/"> 63.102 - <c:out value="${component.name}"/> 63.103 - </a> 63.104 - </div> 63.105 - </c:forEach> 63.106 - </c:if> 63.107 -</c:forEach>
64.1 --- a/src/main/webapp/WEB-INF/jsp/projects.jsp Sat Jan 23 14:47:59 2021 +0100 64.2 +++ b/src/main/webapp/WEB-INF/jsp/projects.jsp Fri Apr 02 11:59:14 2021 +0200 64.3 @@ -28,19 +28,19 @@ 64.4 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 64.5 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 64.6 64.7 -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectView" scope="request"/> 64.8 +<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectsView" scope="request"/> 64.9 64.10 -<c:if test="${empty viewmodel.projectList}"> 64.11 +<c:if test="${empty viewmodel.projects}"> 64.12 <div class="info-box"> 64.13 <fmt:message key="no-projects"/> 64.14 </div> 64.15 </c:if> 64.16 64.17 <div id="tool-area"> 64.18 - <a href="./projects/create" class="button"><fmt:message key="button.project.create"/></a> 64.19 + <a href="./projects/-/create" class="button"><fmt:message key="button.project.create"/></a> 64.20 </div> 64.21 64.22 -<c:if test="${not empty viewmodel.projectList}"> 64.23 +<c:if test="${not empty viewmodel.projects}"> 64.24 <table id="project-list" class="datatable medskip"> 64.25 <colgroup> 64.26 <col> 64.27 @@ -65,11 +65,11 @@ 64.28 </tr> 64.29 </thead> 64.30 <tbody> 64.31 - <c:forEach var="projectInfo" items="${viewmodel.projectList}"> 64.32 + <c:forEach var="projectInfo" items="${viewmodel.projects}"> 64.33 <c:set var="project" scope="page" value="${projectInfo.project}"/> 64.34 <tr class="nowrap"> 64.35 <td style="width: 2em;"><a href="./projects/${project.node}/edit">✎</a></td> 64.36 - <td><a href="./projects/${project.node}/versions/"><c:out value="${project.name}"/></a> 64.37 + <td><a href="./projects/${project.node}"><c:out value="${project.name}"/></a> 64.38 </td> 64.39 <td> 64.40 <c:if test="${not empty project.repoUrl}"> 64.41 @@ -79,12 +79,12 @@ 64.42 </td> 64.43 <td class="hright"> 64.44 <c:if test="${not empty projectInfo.latestVersion}"> 64.45 - <a href="./projects/${project.node}/all-components/${projectInfo.latestVersion.node}/issues/"><c:out value="${projectInfo.latestVersion.name}"/></a> 64.46 + <a href="./projects/${project.node}/issues/${projectInfo.latestVersion.node}/-/"><c:out value="${projectInfo.latestVersion.name}"/></a> 64.47 </c:if> 64.48 </td> 64.49 <td class="hright"> 64.50 <c:if test="${not empty projectInfo.nextVersion}"> 64.51 - <a href="./projects/${project.node}/all-components/${projectInfo.nextVersion.node}/issues/"><c:out value="${projectInfo.nextVersion.name}"/></a> 64.52 + <a href="./projects/${project.node}/issues/${projectInfo.nextVersion.node}/-/"><c:out value="${projectInfo.nextVersion.name}"/></a> 64.53 </c:if> 64.54 </td> 64.55 <td class="hright">${projectInfo.issueSummary.open}</td>
65.1 --- a/src/main/webapp/WEB-INF/jsp/site.jsp Sat Jan 23 14:47:59 2021 +0100 65.2 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Fri Apr 02 11:59:14 2021 +0200 65.3 @@ -79,8 +79,8 @@ 65.4 <fmt:message key="menu.projects"/> 65.5 </a> 65.6 </div> 65.7 - <div class="menuEntry" <c:if test="${fn:startsWith(requestPath, '/teams/')}">data-active</c:if> > 65.8 - <a href="teams/"> 65.9 + <div class="menuEntry" <c:if test="${fn:startsWith(requestPath, '/users/')}">data-active</c:if> > 65.10 + <a href="users/"> 65.11 <fmt:message key="menu.users"/> 65.12 </a> 65.13 </div> 65.14 @@ -93,7 +93,7 @@ 65.15 <div> 65.16 <c:if test="${not empty navMenu}"> 65.17 <div id="sideMenu"> 65.18 - <c:import url="${navMenu}"/> 65.19 + <%@include file="../jspf/navmenu.jspf"%> 65.20 </div> 65.21 </c:if> 65.22 <div id="content-area" <c:if test="${not empty navMenu}">class="sidebar-spacing"</c:if>>
66.1 --- a/src/main/webapp/WEB-INF/jsp/user-form.jsp Sat Jan 23 14:47:59 2021 +0100 66.2 +++ b/src/main/webapp/WEB-INF/jsp/user-form.jsp Fri Apr 02 11:59:14 2021 +0200 66.3 @@ -28,10 +28,10 @@ 66.4 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 66.5 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 66.6 66.7 -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.UsersEditView" scope="request"/> 66.8 +<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.UserEditView" scope="request"/> 66.9 <c:set var="user" scope="page" value="${viewmodel.user}" /> 66.10 66.11 -<form action="./teams/commit" method="post"> 66.12 +<form action="./users/-/commit" method="post"> 66.13 <table class="formtable"> 66.14 <colgroup> 66.15 <col> 66.16 @@ -57,10 +57,19 @@ 66.17 </tr> 66.18 </tbody> 66.19 <tfoot> 66.20 + <c:if test="${not empty viewmodel.errorText}"> 66.21 + <tr> 66.22 + <td colspan="2"> 66.23 + <div class="error-box"> 66.24 + <fmt:message key="${viewmodel.errorText}"/> 66.25 + </div> 66.26 + </td> 66.27 + </tr> 66.28 + </c:if> 66.29 <tr> 66.30 <td colspan="2"> 66.31 <input type="hidden" name="userid" value="${user.id}"/> 66.32 - <a href="./teams/" class="button"> 66.33 + <a href="./users/" class="button"> 66.34 <fmt:message key="button.cancel"/> 66.35 </a> 66.36 <button type="submit"><fmt:message key="button.okay"/></button>
67.1 --- a/src/main/webapp/WEB-INF/jsp/users.jsp Sat Jan 23 14:47:59 2021 +0100 67.2 +++ b/src/main/webapp/WEB-INF/jsp/users.jsp Fri Apr 02 11:59:14 2021 +0200 67.3 @@ -37,7 +37,7 @@ 67.4 </c:if> 67.5 67.6 <div id="tool-area"> 67.7 - <a href="./teams/edit" class="button"><fmt:message key="button.user.create"/></a> 67.8 + <a href="./users/-/create" class="button"><fmt:message key="button.user.create"/></a> 67.9 </div> 67.10 67.11 <c:if test="${not empty viewmodel.users}"> 67.12 @@ -51,7 +51,7 @@ 67.13 <tbody> 67.14 <c:forEach var="user" items="${viewmodel.users}"> 67.15 <tr> 67.16 - <td><a href="./teams/edit?id=${user.id}">✎</a></td> 67.17 + <td><a href="./users/${user.id}/edit">✎</a></td> 67.18 <td><c:out value="${user.displayname}"/></td> 67.19 </tr> 67.20 </c:forEach>
68.1 --- a/src/main/webapp/WEB-INF/jsp/version-form.jsp Sat Jan 23 14:47:59 2021 +0100 68.2 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp Fri Apr 02 11:59:14 2021 +0200 68.3 @@ -32,7 +32,7 @@ 68.4 <c:set var="version" scope="page" value="${viewmodel.version}"/> 68.5 <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> 68.6 68.7 -<form action="./projects/commit-version" method="post"> 68.8 +<form action="./projects/${project.node}/versions/-/commit" method="post"> 68.9 <table class="formtable" style="width: 35ch"> 68.10 <colgroup> 68.11 <col> 68.12 @@ -43,7 +43,7 @@ 68.13 <th><fmt:message key="project"/></th> 68.14 <td> 68.15 <c:out value="${project.name}" /> 68.16 - <input type="hidden" name="pid" value="${project.id}" /> 68.17 + <input type="hidden" name="projectid" value="${project.id}" /> 68.18 </td> 68.19 </tr> 68.20 <tr>
69.1 --- a/src/main/webapp/WEB-INF/jsp/versions.jsp Sat Jan 23 14:47:59 2021 +0100 69.2 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Fri Apr 02 11:59:14 2021 +0200 69.3 @@ -34,8 +34,8 @@ 69.4 <%@include file="../jspf/project-header.jspf"%> 69.5 69.6 <div id="tool-area"> 69.7 - <a href="./projects/${project.node}/create-version" class="button"><fmt:message key="button.version.create"/></a> 69.8 - <a href="./projects/${project.node}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> 69.9 + <a href="./projects/${project.node}/versions/-/create" class="button"><fmt:message key="button.version.create"/></a> 69.10 + <a href="./projects/${project.node}/issues/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> 69.11 </div> 69.12 69.13 <h2><fmt:message key="progress" /></h2> 69.14 @@ -80,7 +80,7 @@ 69.15 <tr> 69.16 <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/versions/${versionInfo.version.node}/edit">✎</a></td> 69.17 <td rowspan="2"> 69.18 - <a href="./projects/${project.node}/all-components/${versionInfo.version.node}/issues/"> 69.19 + <a href="./projects/${project.node}/issues/${versionInfo.version.node}/-/"> 69.20 <c:out value="${versionInfo.version.name}"/> 69.21 </a> 69.22 <div class="version-tag version-${versionInfo.version.status}">
70.1 --- a/src/main/webapp/WEB-INF/jspf/issue-list.jspf Sat Jan 23 14:47:59 2021 +0100 70.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-list.jspf Fri Apr 02 11:59:14 2021 +0200 70.3 @@ -1,5 +1,7 @@ 70.4 <%-- 70.5 issues: List<Issue> 70.6 +version: Version? 70.7 +component: Component? 70.8 --%> 70.9 <table class="fullwidth datatable medskip"> 70.10 <colgroup> 70.11 @@ -17,7 +19,7 @@ 70.12 <tr> 70.13 <td> 70.14 <span class="phase-${issue.status.phase.number}"> 70.15 - <a href="./projects/${issue.project.node}/issues/${issue.id}/view"> 70.16 + <a href="./projects/${issue.project.node}/issues/${empty version ? '-' : version.node }/${empty component ? '-' : component.node}/${issue.id}"> 70.17 #${issue.id} - <c:out value="${issue.subject}" /> 70.18 </a> 70.19 </span>
71.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 71.2 +++ b/src/main/webapp/WEB-INF/jspf/navmenu.jspf Fri Apr 02 11:59:14 2021 +0200 71.3 @@ -0,0 +1,55 @@ 71.4 +<%-- 71.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 71.6 + 71.7 +Copyright 2021 Mike Becker. All rights reserved. 71.8 + 71.9 +Redistribution and use in source and binary forms, with or without 71.10 +modification, are permitted provided that the following conditions are met: 71.11 + 71.12 +1. Redistributions of source code must retain the above copyright 71.13 +notice, this list of conditions and the following disclaimer. 71.14 + 71.15 +2. Redistributions in binary form must reproduce the above copyright 71.16 +notice, this list of conditions and the following disclaimer in the 71.17 +documentation and/or other materials provided with the distribution. 71.18 + 71.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 71.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 71.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 71.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 71.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 71.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 71.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 71.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 71.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 71.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 71.29 +--%> 71.30 +<%@page pageEncoding="UTF-8" %> 71.31 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 71.32 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 71.33 + 71.34 +<jsp:useBean id="navMenu" type="de.uapcore.lightpit.viewmodel.NavMenu" scope="request" /> 71.35 + 71.36 +<c:forEach var="entry" items="${navMenu.entries}"> 71.37 + <div class="menuEntry level-${entry.level}" 71.38 + <c:if test="${entry.active}"> data-active </c:if> 71.39 + <c:if test="${not empty entry.title}">title="<fmt:message key="${entry.title}"/>" </c:if> 71.40 + > 71.41 + <c:if test="${not empty entry.iconColor}"> 71.42 + <c:if test="${entry.iconUseCssClass}"> 71.43 + <div class="navmenu-icon ${entry.iconColor}"></div> 71.44 + </c:if> 71.45 + <c:if test="${not entry.iconUseCssClass}"> 71.46 + <div class="navmenu-icon" style="background: ${entry.iconColor}"></div> 71.47 + </c:if> 71.48 + </c:if> 71.49 + <a href="./${entry.href}"> 71.50 + <c:if test="${entry.resolveCaption}"> 71.51 + <fmt:message key="${entry.caption}"/> 71.52 + </c:if> 71.53 + <c:if test="${not entry.resolveCaption}"> 71.54 + <c:out value="${entry.caption}"/> 71.55 + </c:if> 71.56 + </a> 71.57 + </div> 71.58 +</c:forEach>
72.1 --- a/src/main/webapp/WEB-INF/jspf/project-header.jspf Sat Jan 23 14:47:59 2021 +0100 72.2 +++ b/src/main/webapp/WEB-INF/jspf/project-header.jspf Fri Apr 02 11:59:14 2021 +0200 72.3 @@ -22,7 +22,7 @@ 72.4 </c:if> 72.5 </div> 72.6 </div> 72.7 - <c:if test="${not empty component and component.id gt 0}"> 72.8 + <c:if test="${not empty component}"> 72.9 <div class="row"> 72.10 <div class="caption"><fmt:message key="component"/>:</div> 72.11 <div><c:out value="${component.name}"/></div>