major refactoring of DAO architecture - also fixes #114

Mon, 21 Dec 2020 18:29:34 +0100

author
Mike Becker <universe@uap-core.de>
date
Mon, 21 Dec 2020 18:29:34 +0100
changeset 167
3f30adba1c63
parent 166
6eede6088d41
child 168
1c3694ae224c
child 169
672982f54677

major refactoring of DAO architecture - also fixes #114

src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/UsersModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/Logging.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractChildEntityDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractComponentDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractEntityDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractIssueDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractProjectDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractUserDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/AbstractVersionDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DaoProvider.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/Extensions.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGComponentDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGDaoProvider.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGIssueDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGProjectDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGUserDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGVersionDao.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Component.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Entity.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/IssueSummary.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Project.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/User.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Version.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/filter/Filter.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/filter/IssueFilter.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/IssueCategory.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/IssueStatus.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/IssueStatusPhase.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/VersionStatus.kt file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-navmenu.jsp file | annotate | diff | comparison | revisions
     1.1 --- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sun Dec 20 11:06:25 2020 +0100
     1.2 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Mon Dec 21 18:29:34 2020 +0100
     1.3 @@ -28,8 +28,8 @@
     1.4   */
     1.5  package de.uapcore.lightpit;
     1.6  
     1.7 -import de.uapcore.lightpit.dao.DaoProvider;
     1.8 -import de.uapcore.lightpit.dao.postgres.PGDaoProvider;
     1.9 +import de.uapcore.lightpit.dao.DataAccessObject;
    1.10 +import de.uapcore.lightpit.dao.PostgresDataAccessObject;
    1.11  import org.slf4j.Logger;
    1.12  import org.slf4j.LoggerFactory;
    1.13  
    1.14 @@ -56,26 +56,6 @@
    1.15  
    1.16      private static final String SITE_JSP = jspPath("site");
    1.17  
    1.18 -
    1.19 -    @FunctionalInterface
    1.20 -    protected interface SQLFindFunction<K, T> {
    1.21 -        T apply(K key) throws SQLException;
    1.22 -
    1.23 -        default <V> SQLFindFunction<V, T> compose(Function<? super V, ? extends K> before) throws SQLException {
    1.24 -            Objects.requireNonNull(before);
    1.25 -            return (v) -> this.apply(before.apply(v));
    1.26 -        }
    1.27 -
    1.28 -        default <V> SQLFindFunction<K, V> andThen(Function<? super T, ? extends V> after) throws SQLException {
    1.29 -            Objects.requireNonNull(after);
    1.30 -            return (t) -> after.apply(this.apply(t));
    1.31 -        }
    1.32 -
    1.33 -        static <K> Function<K, K> identity() {
    1.34 -            return (t) -> t;
    1.35 -        }
    1.36 -    }
    1.37 -
    1.38      /**
    1.39       * Invocation mapping gathered from the {@link RequestMapping} annotations.
    1.40       * <p>
    1.41 @@ -101,15 +81,15 @@
    1.42       * @param connection the SQL connection
    1.43       * @return a set of data access objects
    1.44       */
    1.45 -    private DaoProvider createDataAccessObjects(Connection connection) throws SQLException {
    1.46 +    private DataAccessObject createDataAccessObjects(Connection connection) {
    1.47          final var df = (DataSourceProvider) getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME());
    1.48          if (df.getDialect() == DataSourceProvider.Dialect.Postgres) {
    1.49 -            return new PGDaoProvider(connection);
    1.50 +            return new PostgresDataAccessObject(connection);
    1.51          }
    1.52          throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug.");
    1.53      }
    1.54  
    1.55 -    private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException {
    1.56 +    private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException {
    1.57          final var pathPattern = mapping.getKey();
    1.58          final var method = mapping.getValue();
    1.59          try {
    1.60 @@ -122,7 +102,7 @@
    1.61                  } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) {
    1.62                      paramValues[i] = resp;
    1.63                  }
    1.64 -                if (paramTypes[i].isAssignableFrom(DaoProvider.class)) {
    1.65 +                if (paramTypes[i].isAssignableFrom(DataAccessObject.class)) {
    1.66                      paramValues[i] = dao;
    1.67                  }
    1.68                  if (paramTypes[i].isAssignableFrom(PathParameters.class)) {
    1.69 @@ -179,9 +159,9 @@
    1.70                      boolean paramsInjectible = true;
    1.71                      for (var param : method.getParameterTypes()) {
    1.72                          paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param)
    1.73 -                                || HttpServletResponse.class.isAssignableFrom(param)
    1.74 -                                || PathParameters.class.isAssignableFrom(param)
    1.75 -                                || DaoProvider.class.isAssignableFrom(param);
    1.76 +                                            || HttpServletResponse.class.isAssignableFrom(param)
    1.77 +                                            || PathParameters.class.isAssignableFrom(param)
    1.78 +                                            || DataAccessObject.class.isAssignableFrom(param);
    1.79                      }
    1.80                      if (paramsInjectible) {
    1.81                          try {
    1.82 @@ -360,7 +340,7 @@
    1.83       * @return the retrieved entity or an empty optional if there is no such entity or the request parameter was missing
    1.84       * @throws SQLException if the find function throws an exception
    1.85       */
    1.86 -    protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, SQLFindFunction<? super T, ? extends R> find) throws SQLException {
    1.87 +    protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, Function<? super T, ? extends R> find) {
    1.88          final var param = getParameter(req, clazz, name);
    1.89          if (param.isPresent()) {
    1.90              return Optional.ofNullable(find.apply(param.get()));
     2.1 --- a/src/main/java/de/uapcore/lightpit/dao/Functions.java	Sun Dec 20 11:06:25 2020 +0100
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,101 +0,0 @@
     2.4 -/*
     2.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.6 - *
     2.7 - * Copyright 2018 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.dao;
    2.33 -
    2.34 -import java.sql.*;
    2.35 -import java.util.ArrayList;
    2.36 -import java.util.List;
    2.37 -import java.util.Optional;
    2.38 -import java.util.function.Function;
    2.39 -
    2.40 -/**
    2.41 - * Some DAO utilities.
    2.42 - */
    2.43 -public final class Functions {
    2.44 -
    2.45 -    public static String getSafeString(ResultSet rs, String column) throws SQLException {
    2.46 -        return Optional.ofNullable(rs.getString(column)).orElse("");
    2.47 -    }
    2.48 -
    2.49 -    public static void setStringOrNull(PreparedStatement stmt, int index, String str) throws SQLException {
    2.50 -        if (str == null || str.isBlank()) {
    2.51 -            stmt.setNull(index, Types.VARCHAR);
    2.52 -        } else {
    2.53 -            stmt.setString(index, str);
    2.54 -        }
    2.55 -    }
    2.56 -
    2.57 -    public static void setDateOrNull(PreparedStatement stmt, int index, Date date) throws SQLException {
    2.58 -        if (date == null) {
    2.59 -            stmt.setNull(index, Types.DATE);
    2.60 -        } else {
    2.61 -            stmt.setDate(index, date);
    2.62 -        }
    2.63 -    }
    2.64 -
    2.65 -    public static <T> void setForeignKeyOrNull(PreparedStatement stmt, int index, T instance, Function<? super T, Integer> keyGetter) throws SQLException {
    2.66 -        Integer key = Optional.ofNullable(instance).map(keyGetter).orElse(null);
    2.67 -        if (key == null) {
    2.68 -            stmt.setNull(index, Types.INTEGER);
    2.69 -        } else {
    2.70 -            stmt.setInt(index, key);
    2.71 -        }
    2.72 -    }
    2.73 -
    2.74 -    @FunctionalInterface
    2.75 -    public interface ResultSetMapper<T> {
    2.76 -        T apply(ResultSet rs) throws SQLException;
    2.77 -    }
    2.78 -
    2.79 -    public static <T> List<T> list(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException {
    2.80 -        List<T> results = new ArrayList<>();
    2.81 -        try (var result = stmt.executeQuery()) {
    2.82 -            while (result.next()) {
    2.83 -                final var project = mapper.apply(result);
    2.84 -                results.add(project);
    2.85 -            }
    2.86 -        }
    2.87 -        return results;
    2.88 -    }
    2.89 -
    2.90 -    public static <T> T find(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException {
    2.91 -        try (var result = stmt.executeQuery()) {
    2.92 -            if (result.next()) {
    2.93 -                final var ent = mapper.apply(result);
    2.94 -                return ent;
    2.95 -            } else {
    2.96 -                return null;
    2.97 -            }
    2.98 -        }
    2.99 -    }
   2.100 -
   2.101 -    private Functions() {
   2.102 -
   2.103 -    }
   2.104 -}
     3.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Sun Dec 20 11:06:25 2020 +0100
     3.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Mon Dec 21 18:29:34 2020 +0100
     3.3 @@ -30,8 +30,15 @@
     3.4  
     3.5  
     3.6  import de.uapcore.lightpit.*;
     3.7 -import de.uapcore.lightpit.dao.DaoProvider;
     3.8 +import de.uapcore.lightpit.dao.DataAccessObject;
     3.9  import de.uapcore.lightpit.entities.*;
    3.10 +import de.uapcore.lightpit.filter.AllFilter;
    3.11 +import de.uapcore.lightpit.filter.IssueFilter;
    3.12 +import de.uapcore.lightpit.filter.NoneFilter;
    3.13 +import de.uapcore.lightpit.filter.SpecificFilter;
    3.14 +import de.uapcore.lightpit.types.IssueCategory;
    3.15 +import de.uapcore.lightpit.types.IssueStatus;
    3.16 +import de.uapcore.lightpit.types.VersionStatus;
    3.17  import de.uapcore.lightpit.types.WebColor;
    3.18  import de.uapcore.lightpit.viewmodel.*;
    3.19  import de.uapcore.lightpit.viewmodel.util.IssueSorter;
    3.20 @@ -45,7 +52,6 @@
    3.21  import java.io.IOException;
    3.22  import java.sql.Date;
    3.23  import java.sql.SQLException;
    3.24 -import java.util.List;
    3.25  import java.util.NoSuchElementException;
    3.26  import java.util.Optional;
    3.27  import java.util.stream.Collectors;
    3.28 @@ -72,25 +78,21 @@
    3.29          }
    3.30      }
    3.31  
    3.32 -    private void populate(ProjectView viewModel, PathParameters pathParameters, DaoProvider dao) {
    3.33 -        final var projectDao = dao.getProjectDao();
    3.34 -        final var versionDao = dao.getVersionDao();
    3.35 -        final var componentDao = dao.getComponentDao();
    3.36 -
    3.37 -        projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
    3.38 +    private void populate(ProjectView viewModel, PathParameters pathParameters, DataAccessObject dao) {
    3.39 +        dao.listProjects().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
    3.40  
    3.41          if (pathParameters == null)
    3.42              return;
    3.43  
    3.44          // Select Project
    3.45 -        final var project = projectDao.findByNode(pathParameters.get("project"));
    3.46 +        final var project = dao.findProjectByNode(pathParameters.get("project"));
    3.47          if (project == null)
    3.48              return;
    3.49  
    3.50          final var info = new ProjectInfo(project);
    3.51 -        info.setVersions(versionDao.list(project));
    3.52 -        info.setComponents(componentDao.list(project));
    3.53 -        info.setIssueSummary(projectDao.getIssueSummary(project));
    3.54 +        info.setVersions(dao.listVersions(project));
    3.55 +        info.setComponents(dao.listComponents(project));
    3.56 +        info.setIssueSummary(dao.collectIssueSummary(project));
    3.57          viewModel.setProjectInfo(info);
    3.58  
    3.59          // Select Version
    3.60 @@ -101,7 +103,7 @@
    3.61              } else if ("all-versions".equals(versionNode)) {
    3.62                  viewModel.setVersionFilter(ProjectView.ALL_VERSIONS);
    3.63              } else {
    3.64 -                viewModel.setVersionFilter(versionDao.findByNode(project, versionNode));
    3.65 +                viewModel.setVersionFilter(dao.findVersionByNode(project, versionNode));
    3.66              }
    3.67          }
    3.68  
    3.69 @@ -113,7 +115,7 @@
    3.70              } else if ("all-components".equals(componentNode)) {
    3.71                  viewModel.setComponentFilter(ProjectView.ALL_COMPONENTS);
    3.72              } else {
    3.73 -                viewModel.setComponentFilter(componentDao.findByNode(project, componentNode));
    3.74 +                viewModel.setComponentFilter(dao.findComponentByNode(project, componentNode));
    3.75              }
    3.76          }
    3.77      }
    3.78 @@ -137,28 +139,25 @@
    3.79      }
    3.80  
    3.81      @RequestMapping(method = HttpMethod.GET)
    3.82 -    public void index(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException {
    3.83 +    public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException {
    3.84          final var viewModel = new ProjectView();
    3.85          populate(viewModel, null, dao);
    3.86  
    3.87 -        final var projectDao = dao.getProjectDao();
    3.88 -        final var versionDao = dao.getVersionDao();
    3.89 -
    3.90          for (var info : viewModel.getProjectList()) {
    3.91 -            info.setVersions(versionDao.list(info.getProject()));
    3.92 -            info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
    3.93 +            info.setVersions(dao.listVersions(info.getProject()));
    3.94 +            info.setIssueSummary(dao.collectIssueSummary(info.getProject()));
    3.95          }
    3.96  
    3.97          forwardView(req, resp, viewModel, "projects");
    3.98      }
    3.99  
   3.100 -    private void configureProjectEditor(ProjectEditView viewModel, Project project, DaoProvider dao) throws SQLException {
   3.101 +    private void configureProjectEditor(ProjectEditView viewModel, Project project, DataAccessObject dao) {
   3.102          viewModel.setProject(project);
   3.103 -        viewModel.setUsers(dao.getUserDao().list());
   3.104 +        viewModel.setUsers(dao.listUsers());
   3.105      }
   3.106  
   3.107      @RequestMapping(requestPath = "$project/edit", method = HttpMethod.GET)
   3.108 -    public void edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.109 +    public void edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws IOException, SQLException, ServletException {
   3.110          final var viewModel = new ProjectEditView();
   3.111          populate(viewModel, pathParams, dao);
   3.112  
   3.113 @@ -172,7 +171,7 @@
   3.114      }
   3.115  
   3.116      @RequestMapping(requestPath = "create", method = HttpMethod.GET)
   3.117 -    public void create(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException {
   3.118 +    public void create(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException {
   3.119          final var viewModel = new ProjectEditView();
   3.120          populate(viewModel, null, dao);
   3.121          configureProjectEditor(viewModel, new Project(-1), dao);
   3.122 @@ -180,7 +179,7 @@
   3.123      }
   3.124  
   3.125      @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
   3.126 -    public void commit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException {
   3.127 +    public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException {
   3.128  
   3.129          try {
   3.130              final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
   3.131 @@ -195,12 +194,10 @@
   3.132                      ownerId -> ownerId >= 0 ? new User(ownerId) : null
   3.133              ).ifPresent(project::setOwner);
   3.134  
   3.135 -            final var projectDao = dao.getProjectDao();
   3.136              if (project.getId() > 0) {
   3.137 -                // TODO: unused return value
   3.138 -                projectDao.update(project);
   3.139 +                dao.updateProject(project);
   3.140              } else {
   3.141 -                projectDao.save(project);
   3.142 +                dao.insertProject(project);
   3.143              }
   3.144  
   3.145              setRedirectLocation(req, "./projects/");
   3.146 @@ -215,7 +212,7 @@
   3.147      }
   3.148  
   3.149      @RequestMapping(requestPath = "$project/$component/$version/issues/", method = HttpMethod.GET)
   3.150 -    public void issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DaoProvider dao) throws SQLException, IOException, ServletException {
   3.151 +    public void issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws SQLException, IOException, ServletException {
   3.152          final var viewModel = new ProjectDetailsView();
   3.153          populate(viewModel, pathParams, dao);
   3.154  
   3.155 @@ -228,36 +225,64 @@
   3.156          final var version = viewModel.getVersionFilter();
   3.157          final var component = viewModel.getComponentFilter();
   3.158  
   3.159 -        final var issueDao = dao.getIssueDao();
   3.160 +        // TODO: use new IssueFilter class for the ViewModel
   3.161  
   3.162 -        final List<Issue> issues;
   3.163 +        final var projectFilter = new SpecificFilter<>(project);
   3.164 +        final IssueFilter filter;
   3.165          if (version.equals(ProjectView.NO_VERSION)) {
   3.166              if (component.equals(ProjectView.ALL_COMPONENTS)) {
   3.167 -                issues = issueDao.list(project, (Version) null);
   3.168 +                filter = new IssueFilter(projectFilter,
   3.169 +                        new NoneFilter<>(),
   3.170 +                        new AllFilter<>()
   3.171 +                );
   3.172              } else if (component.equals(ProjectView.NO_COMPONENT)) {
   3.173 -                issues = issueDao.list(project, null, null);
   3.174 +                filter = new IssueFilter(projectFilter,
   3.175 +                        new NoneFilter<>(),
   3.176 +                        new NoneFilter<>()
   3.177 +                );
   3.178              } else {
   3.179 -                issues = issueDao.list(project, component, null);
   3.180 +                filter = new IssueFilter(projectFilter,
   3.181 +                        new NoneFilter<>(),
   3.182 +                        new SpecificFilter<>(component)
   3.183 +                );
   3.184              }
   3.185          } else if (version.equals(ProjectView.ALL_VERSIONS)) {
   3.186              if (component.equals(ProjectView.ALL_COMPONENTS)) {
   3.187 -                issues = issueDao.list(project);
   3.188 +                filter = new IssueFilter(projectFilter,
   3.189 +                        new AllFilter<>(),
   3.190 +                        new AllFilter<>()
   3.191 +                );
   3.192              } else if (component.equals(ProjectView.NO_COMPONENT)) {
   3.193 -                issues = issueDao.list(project, (Component)null);
   3.194 +                filter = new IssueFilter(projectFilter,
   3.195 +                        new AllFilter<>(),
   3.196 +                        new NoneFilter<>()
   3.197 +                );
   3.198              } else {
   3.199 -                issues = issueDao.list(project, component);
   3.200 +                filter = new IssueFilter(projectFilter,
   3.201 +                        new AllFilter<>(),
   3.202 +                        new SpecificFilter<>(component)
   3.203 +                );
   3.204              }
   3.205          } else {
   3.206              if (component.equals(ProjectView.ALL_COMPONENTS)) {
   3.207 -                issues = issueDao.list(project, version);
   3.208 +                filter = new IssueFilter(projectFilter,
   3.209 +                        new SpecificFilter<>(version),
   3.210 +                        new AllFilter<>()
   3.211 +                );
   3.212              } else if (component.equals(ProjectView.NO_COMPONENT)) {
   3.213 -                issues = issueDao.list(project, null, version);
   3.214 +                filter = new IssueFilter(projectFilter,
   3.215 +                        new SpecificFilter<>(version),
   3.216 +                        new NoneFilter<>()
   3.217 +                );
   3.218              } else {
   3.219 -                issues = issueDao.list(project, component, version);
   3.220 +                filter = new IssueFilter(projectFilter,
   3.221 +                        new SpecificFilter<>(version),
   3.222 +                        new SpecificFilter<>(component)
   3.223 +                );
   3.224              }
   3.225          }
   3.226  
   3.227 -        for (var issue : issues) issueDao.joinVersionInformation(issue);
   3.228 +        final var issues = dao.listIssues(filter);
   3.229          issues.sort(new IssueSorter(
   3.230                  new IssueSorter.Criteria(IssueSorter.Field.DONE, true),
   3.231                  new IssueSorter.Criteria(IssueSorter.Field.ETA, true),
   3.232 @@ -273,7 +298,7 @@
   3.233      }
   3.234  
   3.235      @RequestMapping(requestPath = "$project/versions/", method = HttpMethod.GET)
   3.236 -    public void versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.237 +    public void versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException {
   3.238          final var viewModel = new VersionsView();
   3.239          populate(viewModel, pathParameters, dao);
   3.240  
   3.241 @@ -283,16 +308,20 @@
   3.242              return;
   3.243          }
   3.244  
   3.245 -        final var issueDao = dao.getIssueDao();
   3.246 -        final var issues = issueDao.list(projectInfo.getProject());
   3.247 -        for (var issue : issues) issueDao.joinVersionInformation(issue);
   3.248 +        final var issues = dao.listIssues(
   3.249 +                new IssueFilter(
   3.250 +                        new SpecificFilter<>(projectInfo.getProject()),
   3.251 +                        new AllFilter<>(),
   3.252 +                        new AllFilter<>()
   3.253 +                )
   3.254 +        );
   3.255          viewModel.update(projectInfo.getVersions(), issues);
   3.256  
   3.257          forwardView(req, resp, viewModel, "versions");
   3.258      }
   3.259  
   3.260      @RequestMapping(requestPath = "$project/versions/$version/edit", method = HttpMethod.GET)
   3.261 -    public void editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.262 +    public void editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException {
   3.263          final var viewModel = new VersionEditView();
   3.264          populate(viewModel, pathParameters, dao);
   3.265  
   3.266 @@ -307,7 +336,7 @@
   3.267      }
   3.268  
   3.269      @RequestMapping(requestPath = "$project/create-version", method = HttpMethod.GET)
   3.270 -    public void createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.271 +    public void createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException {
   3.272          final var viewModel = new VersionEditView();
   3.273          populate(viewModel, pathParameters, dao);
   3.274  
   3.275 @@ -316,22 +345,22 @@
   3.276              return;
   3.277          }
   3.278  
   3.279 -        viewModel.setVersion(new Version(-1));
   3.280 +        viewModel.setVersion(new Version(-1, viewModel.getProjectInfo().getProject().getId()));
   3.281  
   3.282          forwardView(req, resp, viewModel, "version-form");
   3.283      }
   3.284  
   3.285      @RequestMapping(requestPath = "commit-version", method = HttpMethod.POST)
   3.286 -    public void commitVersion(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException {
   3.287 +    public void commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException {
   3.288  
   3.289          try {
   3.290 -            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
   3.291 +            final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow());
   3.292              if (project == null) {
   3.293                  // TODO: improve error handling, because not found is not correct for this POST request
   3.294                  resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.295                  return;
   3.296              }
   3.297 -            final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
   3.298 +            final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), project.getId());
   3.299              version.setName(getParameter(req, String.class, "name").orElseThrow());
   3.300  
   3.301              final var node = getParameter(req, String.class, "node").orElse(null);
   3.302 @@ -340,12 +369,10 @@
   3.303              getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
   3.304              version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
   3.305  
   3.306 -            final var versionDao = dao.getVersionDao();
   3.307              if (version.getId() > 0) {
   3.308 -                // TODO: use return value
   3.309 -                versionDao.update(version);
   3.310 +                dao.updateVersion(version);
   3.311              } else {
   3.312 -                versionDao.save(version, project);
   3.313 +                dao.insertVersion(version);
   3.314              }
   3.315  
   3.316              setRedirectLocation(req, "./projects/" + project.getNode() + "/versions/");
   3.317 @@ -359,7 +386,7 @@
   3.318      }
   3.319  
   3.320      @RequestMapping(requestPath = "$project/components/", method = HttpMethod.GET)
   3.321 -    public void components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.322 +    public void components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException {
   3.323          final var viewModel = new ComponentsView();
   3.324          populate(viewModel, pathParameters, dao);
   3.325  
   3.326 @@ -369,15 +396,20 @@
   3.327              return;
   3.328          }
   3.329  
   3.330 -        final var issueDao = dao.getIssueDao();
   3.331 -        final var issues = issueDao.list(projectInfo.getProject());
   3.332 +        final var issues = dao.listIssues(
   3.333 +                new IssueFilter(
   3.334 +                        new SpecificFilter<>(projectInfo.getProject()),
   3.335 +                        new AllFilter<>(),
   3.336 +                        new AllFilter<>()
   3.337 +                )
   3.338 +        );
   3.339          viewModel.update(projectInfo.getComponents(), issues);
   3.340  
   3.341          forwardView(req, resp, viewModel, "components");
   3.342      }
   3.343  
   3.344      @RequestMapping(requestPath = "$project/components/$component/edit", method = HttpMethod.GET)
   3.345 -    public void editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.346 +    public void editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException {
   3.347          final var viewModel = new ComponentEditView();
   3.348          populate(viewModel, pathParameters, dao);
   3.349  
   3.350 @@ -387,13 +419,13 @@
   3.351          }
   3.352  
   3.353          viewModel.setComponent(viewModel.getComponentFilter());
   3.354 -        viewModel.setUsers(dao.getUserDao().list());
   3.355 +        viewModel.setUsers(dao.listUsers());
   3.356  
   3.357          forwardView(req, resp, viewModel, "component-form");
   3.358      }
   3.359  
   3.360      @RequestMapping(requestPath = "$project/create-component", method = HttpMethod.GET)
   3.361 -    public void createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.362 +    public void createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException {
   3.363          final var viewModel = new ComponentEditView();
   3.364          populate(viewModel, pathParameters, dao);
   3.365  
   3.366 @@ -402,23 +434,23 @@
   3.367              return;
   3.368          }
   3.369  
   3.370 -        viewModel.setComponent(new Component(-1));
   3.371 -        viewModel.setUsers(dao.getUserDao().list());
   3.372 +        viewModel.setComponent(new Component(-1, viewModel.getProjectInfo().getProject().getId()));
   3.373 +        viewModel.setUsers(dao.listUsers());
   3.374  
   3.375          forwardView(req, resp, viewModel, "component-form");
   3.376      }
   3.377  
   3.378      @RequestMapping(requestPath = "commit-component", method = HttpMethod.POST)
   3.379 -    public void commitComponent(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException {
   3.380 +    public void commitComponent(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException {
   3.381  
   3.382          try {
   3.383 -            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
   3.384 +            final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow());
   3.385              if (project == null) {
   3.386                  // TODO: improve error handling, because not found is not correct for this POST request
   3.387                  resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.388                  return;
   3.389              }
   3.390 -            final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow());
   3.391 +            final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow(), project.getId());
   3.392              component.setName(getParameter(req, String.class, "name").orElseThrow());
   3.393  
   3.394              final var node = getParameter(req, String.class, "node").orElse(null);
   3.395 @@ -431,12 +463,10 @@
   3.396              ).ifPresent(component::setLead);
   3.397              getParameter(req, String.class, "description").ifPresent(component::setDescription);
   3.398  
   3.399 -            final var componentDao = dao.getComponentDao();
   3.400              if (component.getId() > 0) {
   3.401 -                // TODO: use return value
   3.402 -                componentDao.update(component);
   3.403 +                dao.updateComponent(component);
   3.404              } else {
   3.405 -                componentDao.save(component, project);
   3.406 +                dao.insertComponent(component);
   3.407              }
   3.408  
   3.409              setRedirectLocation(req, "./projects/" + project.getNode() + "/components/");
   3.410 @@ -449,17 +479,17 @@
   3.411          }
   3.412      }
   3.413  
   3.414 -    private void configureIssueEditor(IssueEditView viewModel, Issue issue, DaoProvider dao) throws SQLException {
   3.415 +    private void configureIssueEditor(IssueEditView viewModel, Issue issue, DataAccessObject dao) {
   3.416          final var project = viewModel.getProjectInfo().getProject();
   3.417          issue.setProject(project); // automatically set current project for new issues
   3.418          viewModel.setIssue(issue);
   3.419          viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
   3.420 -        viewModel.setUsers(dao.getUserDao().list());
   3.421 -        viewModel.setComponents(dao.getComponentDao().list(project));
   3.422 +        viewModel.setUsers(dao.listUsers());
   3.423 +        viewModel.setComponents(dao.listComponents(project));
   3.424      }
   3.425  
   3.426      @RequestMapping(requestPath = "$project/issues/$issue/view", method = HttpMethod.GET)
   3.427 -    public void viewIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.428 +    public void viewIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException {
   3.429          final var viewModel = new IssueDetailView();
   3.430          populate(viewModel, pathParameters, dao);
   3.431  
   3.432 @@ -469,16 +499,14 @@
   3.433              return;
   3.434          }
   3.435  
   3.436 -        final var issueDao = dao.getIssueDao();
   3.437 -        final var issue = issueDao.find(parseIntOrZero(pathParameters.get("issue")));
   3.438 +        final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue")));
   3.439          if (issue == null) {
   3.440              resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.441              return;
   3.442          }
   3.443  
   3.444 -        issueDao.joinVersionInformation(issue);
   3.445          viewModel.setIssue(issue);
   3.446 -        viewModel.setComments(issueDao.listComments(issue));
   3.447 +        viewModel.setComments(dao.listComments(issue));
   3.448  
   3.449          viewModel.processMarkdown();
   3.450  
   3.451 @@ -487,7 +515,7 @@
   3.452  
   3.453      // TODO: why should the issue editor be child of $project?
   3.454      @RequestMapping(requestPath = "$project/issues/$issue/edit", method = HttpMethod.GET)
   3.455 -    public void editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.456 +    public void editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException {
   3.457          final var viewModel = new IssueEditView();
   3.458          populate(viewModel, pathParameters, dao);
   3.459  
   3.460 @@ -497,21 +525,19 @@
   3.461              return;
   3.462          }
   3.463  
   3.464 -        final var issueDao = dao.getIssueDao();
   3.465 -        final var issue = issueDao.find(parseIntOrZero(pathParameters.get("issue")));
   3.466 +        final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue")));
   3.467          if (issue == null) {
   3.468              resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.469              return;
   3.470          }
   3.471  
   3.472 -        issueDao.joinVersionInformation(issue);
   3.473          configureIssueEditor(viewModel, issue, dao);
   3.474  
   3.475          forwardView(req, resp, viewModel, "issue-form");
   3.476      }
   3.477  
   3.478      @RequestMapping(requestPath = "$project/create-issue", method = HttpMethod.GET)
   3.479 -    public void createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException {
   3.480 +    public void createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException {
   3.481          final var viewModel = new IssueEditView();
   3.482          populate(viewModel, pathParameters, dao);
   3.483  
   3.484 @@ -521,7 +547,8 @@
   3.485              return;
   3.486          }
   3.487  
   3.488 -        final var issue = new Issue(-1);
   3.489 +        // TODO: fix #38 - automatically select component (and version)
   3.490 +        final var issue = new Issue(-1, projectInfo.getProject(), null);
   3.491          issue.setProject(projectInfo.getProject());
   3.492          configureIssueEditor(viewModel, issue, dao);
   3.493  
   3.494 @@ -529,27 +556,25 @@
   3.495      }
   3.496  
   3.497      @RequestMapping(requestPath = "commit-issue", method = HttpMethod.POST)
   3.498 -    public void commitIssue(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException {
   3.499 +    public void commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException {
   3.500          try {
   3.501 -            final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
   3.502 -            final var componentId = getParameter(req, Integer.class, "component");
   3.503 -            final Component component;
   3.504 -            if (componentId.isPresent()) {
   3.505 -                component = dao.getComponentDao().find(componentId.get());
   3.506 -            } else {
   3.507 -                component = null;
   3.508 -            }
   3.509 -            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
   3.510 +            final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow());
   3.511              if (project == null) {
   3.512                  // TODO: improve error handling, because not found is not correct for this POST request
   3.513                  resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.514                  return;
   3.515              }
   3.516 -            issue.setProject(project);
   3.517 +            final var componentId = getParameter(req, Integer.class, "component");
   3.518 +            final Component component;
   3.519 +            if (componentId.isPresent()) {
   3.520 +                component = dao.findComponent(componentId.get());
   3.521 +            } else {
   3.522 +                component = null;
   3.523 +            }
   3.524 +            final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), project, component);
   3.525              getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
   3.526              getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
   3.527              issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
   3.528 -            issue.setComponent(component);
   3.529              getParameter(req, Integer.class, "assignee").map(userid -> {
   3.530                  if (userid >= 0) {
   3.531                      return new User(userid);
   3.532 @@ -566,23 +591,23 @@
   3.533              getParameter(req, Integer[].class, "affected")
   3.534                      .map(Stream::of)
   3.535                      .map(stream ->
   3.536 -                            stream.map(Version::new).collect(Collectors.toList())
   3.537 +                            stream.map(id -> new Version(id, project.getId()))
   3.538 +                                    .collect(Collectors.toList())
   3.539                      ).ifPresent(issue::setAffectedVersions);
   3.540              getParameter(req, Integer[].class, "resolved")
   3.541                      .map(Stream::of)
   3.542                      .map(stream ->
   3.543 -                            stream.map(Version::new).collect(Collectors.toList())
   3.544 +                            stream.map(id -> new Version(id, project.getId()))
   3.545 +                                    .collect(Collectors.toList())
   3.546                      ).ifPresent(issue::setResolvedVersions);
   3.547  
   3.548 -            final var issueDao = dao.getIssueDao();
   3.549              if (issue.getId() > 0) {
   3.550 -                // TODO: use return value
   3.551 -                issueDao.update(issue);
   3.552 +                dao.updateIssue(issue);
   3.553              } else {
   3.554 -                issueDao.save(issue, project);
   3.555 +                dao.insertIssue(issue);
   3.556              }
   3.557  
   3.558 -            // TODO: fix redirect location
   3.559 +            // TODO: implement #110
   3.560              setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view");
   3.561              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   3.562  
   3.563 @@ -594,19 +619,19 @@
   3.564      }
   3.565  
   3.566      @RequestMapping(requestPath = "commit-issue-comment", method = HttpMethod.POST)
   3.567 -    public void commentIssue(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException {
   3.568 +    public void commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException {
   3.569          final var issueIdParam = getParameter(req, Integer.class, "issueid");
   3.570          if (issueIdParam.isEmpty()) {
   3.571              resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Detected manipulated form.");
   3.572              return;
   3.573          }
   3.574 -        final var issue = dao.getIssueDao().find(issueIdParam.get());
   3.575 +        final var issue = dao.findIssue(issueIdParam.get());
   3.576          if (issue == null) {
   3.577              resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   3.578              return;
   3.579          }
   3.580          try {
   3.581 -            final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1));
   3.582 +            final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue.getId());
   3.583              issueComment.setComment(getParameter(req, String.class, "comment").orElse(""));
   3.584  
   3.585              if (issueComment.getComment().isBlank()) {
   3.586 @@ -615,12 +640,11 @@
   3.587  
   3.588              LOG.debug("User {} is commenting on issue #{}", req.getRemoteUser(), issue.getId());
   3.589              if (req.getRemoteUser() != null) {
   3.590 -                Optional.ofNullable(dao.getUserDao().findByUsername(req.getRemoteUser())).ifPresent(issueComment::setAuthor);
   3.591 +                Optional.ofNullable(dao.findUserByName(req.getRemoteUser())).ifPresent(issueComment::setAuthor);
   3.592              }
   3.593  
   3.594 -            dao.getIssueDao().saveComment(issue, issueComment);
   3.595 +            dao.insertComment(issueComment);
   3.596  
   3.597 -            // TODO: fix redirect location
   3.598              setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view");
   3.599              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   3.600  
     4.1 --- a/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Sun Dec 20 11:06:25 2020 +0100
     4.2 +++ b/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Mon Dec 21 18:29:34 2020 +0100
     4.3 @@ -32,7 +32,7 @@
     4.4  import de.uapcore.lightpit.Constants;
     4.5  import de.uapcore.lightpit.HttpMethod;
     4.6  import de.uapcore.lightpit.RequestMapping;
     4.7 -import de.uapcore.lightpit.dao.DaoProvider;
     4.8 +import de.uapcore.lightpit.dao.DataAccessObject;
     4.9  import de.uapcore.lightpit.entities.User;
    4.10  import de.uapcore.lightpit.viewmodel.UsersEditView;
    4.11  import de.uapcore.lightpit.viewmodel.UsersView;
    4.12 @@ -61,11 +61,9 @@
    4.13      }
    4.14  
    4.15      @RequestMapping(method = HttpMethod.GET)
    4.16 -    public void index(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException {
    4.17 -        final var userDao = dao.getUserDao();
    4.18 -
    4.19 +    public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException {
    4.20          final var viewModel = new UsersView();
    4.21 -        viewModel.setUsers(userDao.list());
    4.22 +        viewModel.setUsers(dao.listUsers());
    4.23          setViewModel(req, viewModel);
    4.24          setContentPage(req, "users");
    4.25  
    4.26 @@ -73,11 +71,10 @@
    4.27      }
    4.28  
    4.29      @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
    4.30 -    public void edit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException {
    4.31 +    public void edit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException {
    4.32  
    4.33          final var viewModel = new UsersEditView();
    4.34 -        viewModel.setUser(findByParameter(req, Integer.class, "id",
    4.35 -                dao.getUserDao()::find).orElse(new User(-1)));
    4.36 +        viewModel.setUser(findByParameter(req, Integer.class, "id", dao::findUser).orElse(new User(-1)));
    4.37  
    4.38          setViewModel(req, viewModel);
    4.39          setContentPage(req, "user-form");
    4.40 @@ -86,7 +83,7 @@
    4.41      }
    4.42  
    4.43      @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
    4.44 -    public void commit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws ServletException, IOException {
    4.45 +    public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException {
    4.46  
    4.47          User user = new User(-1);
    4.48          try {
    4.49 @@ -96,12 +93,10 @@
    4.50              getParameter(req, String.class, "lastname").ifPresent(user::setLastname);
    4.51              getParameter(req, String.class, "mail").ifPresent(user::setMail);
    4.52  
    4.53 -            final var userDao = dao.getUserDao();
    4.54              if (user.getId() > 0) {
    4.55 -                // TODO: unused return value
    4.56 -                userDao.update(user);
    4.57 +                dao.updateUser(user);
    4.58              } else {
    4.59 -                userDao.save(user);
    4.60 +                dao.insertUser(user);
    4.61              }
    4.62  
    4.63              setRedirectLocation(req, "./teams/");
     5.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java	Sun Dec 20 11:06:25 2020 +0100
     5.2 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java	Mon Dec 21 18:29:34 2020 +0100
     5.3 @@ -1,6 +1,12 @@
     5.4  package de.uapcore.lightpit.viewmodel;
     5.5  
     5.6 -import de.uapcore.lightpit.entities.*;
     5.7 +import de.uapcore.lightpit.entities.Component;
     5.8 +import de.uapcore.lightpit.entities.Project;
     5.9 +import de.uapcore.lightpit.entities.User;
    5.10 +import de.uapcore.lightpit.entities.Version;
    5.11 +import de.uapcore.lightpit.types.IssueCategory;
    5.12 +import de.uapcore.lightpit.types.IssueStatus;
    5.13 +import de.uapcore.lightpit.types.VersionStatus;
    5.14  
    5.15  import java.util.*;
    5.16  
     6.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java	Sun Dec 20 11:06:25 2020 +0100
     6.2 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java	Mon Dec 21 18:29:34 2020 +0100
     6.3 @@ -12,10 +12,12 @@
     6.4      public static final int SELECTED_PAGE_VERSIONS = 1;
     6.5      public static final int SELECTED_PAGE_COMPONENTS = 2;
     6.6  
     6.7 -    public static final Version ALL_VERSIONS = new Version(0);
     6.8 -    public static final Version NO_VERSION = new Version(-1);
     6.9 -    public static final Component ALL_COMPONENTS = new Component(0);
    6.10 -    public static final Component NO_COMPONENT = new Component(-1);
    6.11 +    // TODO: use new Filter class
    6.12 +
    6.13 +    public static final Version ALL_VERSIONS = new Version(0,0);
    6.14 +    public static final Version NO_VERSION = new Version(-1,0);
    6.15 +    public static final Component ALL_COMPONENTS = new Component(0,0);
    6.16 +    public static final Component NO_COMPONENT = new Component(-1,0);
    6.17  
    6.18      static {
    6.19          ALL_VERSIONS.setNode("all-versions");
     7.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java	Sun Dec 20 11:06:25 2020 +0100
     7.2 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java	Mon Dec 21 18:29:34 2020 +0100
     7.3 @@ -1,7 +1,7 @@
     7.4  package de.uapcore.lightpit.viewmodel;
     7.5  
     7.6  import de.uapcore.lightpit.entities.Version;
     7.7 -import de.uapcore.lightpit.entities.VersionStatus;
     7.8 +import de.uapcore.lightpit.types.VersionStatus;
     7.9  
    7.10  public class VersionEditView extends ProjectView {
    7.11      private Version version;
     8.1 --- a/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java	Sun Dec 20 11:06:25 2020 +0100
     8.2 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java	Mon Dec 21 18:29:34 2020 +0100
     8.3 @@ -1,7 +1,7 @@
     8.4  package de.uapcore.lightpit.viewmodel.util;
     8.5  
     8.6  import de.uapcore.lightpit.entities.Issue;
     8.7 -import de.uapcore.lightpit.entities.IssueStatusPhase;
     8.8 +import de.uapcore.lightpit.types.IssueStatusPhase;
     8.9  
    8.10  import java.util.Arrays;
    8.11  import java.util.Comparator;
     9.1 --- a/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt	Sun Dec 20 11:06:25 2020 +0100
     9.2 +++ b/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt	Mon Dec 21 18:29:34 2020 +0100
     9.3 @@ -128,7 +128,7 @@
     9.4          val sc = sce!!.servletContext
     9.5  
     9.6          val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA
     9.7 -        sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let {dbDialect ->
     9.8 +        sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let { dbDialect ->
     9.9              try {
    9.10                  dialect = Dialect.valueOf(dbDialect)
    9.11              } catch (ex: IllegalArgumentException) {
    10.1 --- a/src/main/kotlin/de/uapcore/lightpit/Logging.kt	Sun Dec 20 11:06:25 2020 +0100
    10.2 +++ b/src/main/kotlin/de/uapcore/lightpit/Logging.kt	Mon Dec 21 18:29:34 2020 +0100
    10.3 @@ -29,4 +29,5 @@
    10.4  import org.slf4j.LoggerFactory
    10.5  
    10.6  interface LoggingTrait
    10.7 -inline fun <reified T : LoggingTrait> T.logger(): Logger = LoggerFactory.getLogger(T::class.java);
    10.8 +
    10.9 +inline fun <reified T : LoggingTrait> T.logger(): Logger = LoggerFactory.getLogger(T::class.java)
    11.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractChildEntityDao.kt	Sun Dec 20 11:06:25 2020 +0100
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,64 +0,0 @@
    11.4 -/*
    11.5 - * Copyright 2020 Mike Becker. All rights reserved.
    11.6 - *
    11.7 - * Redistribution and use in source and binary forms, with or without
    11.8 - * modification, are permitted provided that the following conditions are met:
    11.9 - *
   11.10 - * 1. Redistributions of source code must retain the above copyright
   11.11 - * notice, this list of conditions and the following disclaimer.
   11.12 - *
   11.13 - * 2. Redistributions in binary form must reproduce the above copyright
   11.14 - * notice, this list of conditions and the following disclaimer in the
   11.15 - * documentation and/or other materials provided with the distribution.
   11.16 - *
   11.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   11.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   11.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   11.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   11.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   11.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   11.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   11.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   11.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   11.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   11.27 - *
   11.28 - */
   11.29 -
   11.30 -package de.uapcore.lightpit.dao
   11.31 -
   11.32 -
   11.33 -abstract class AbstractChildEntityDao<T, P> : AbstractDao<T>() {
   11.34 -
   11.35 -    /**
   11.36 -     * Lists all entities being a child of the specified parent.
   11.37 -     * @param parent the parent
   11.38 -     * @return the list of child instances
   11.39 -     */
   11.40 -    abstract fun list(parent: P): List<T>
   11.41 -
   11.42 -    /**
   11.43 -     * Finds an entity by its integer ID.
   11.44 -     * It is not guaranteed that referenced entities are automatically joined.
   11.45 -     *
   11.46 -     * @param id the id
   11.47 -     * @return the entity or null if there is no such entity
   11.48 -     */
   11.49 -    abstract fun find(id: Int): T?
   11.50 -
   11.51 -    /**
   11.52 -     * Inserts an instance into database.
   11.53 -     * It is not guaranteed that generated fields will be updated in the instance.
   11.54 -     *
   11.55 -     * @param instance the instance to insert
   11.56 -     * @param parent a reference to the parent
   11.57 -     */
   11.58 -    abstract fun save(instance: T, parent: P)
   11.59 -
   11.60 -    /**
   11.61 -     * Updates an instance in the database.
   11.62 -     *
   11.63 -     * @param instance the instance to update
   11.64 -     * @return true if an instance has been updated, false if the instance is not present in database
   11.65 -     */
   11.66 -    abstract fun update(instance: T): Boolean
   11.67 -}
    12.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractComponentDao.kt	Sun Dec 20 11:06:25 2020 +0100
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,34 +0,0 @@
    12.4 -/*
    12.5 - * Copyright 2020 Mike Becker. All rights reserved.
    12.6 - *
    12.7 - * Redistribution and use in source and binary forms, with or without
    12.8 - * modification, are permitted provided that the following conditions are met:
    12.9 - *
   12.10 - * 1. Redistributions of source code must retain the above copyright
   12.11 - * notice, this list of conditions and the following disclaimer.
   12.12 - *
   12.13 - * 2. Redistributions in binary form must reproduce the above copyright
   12.14 - * notice, this list of conditions and the following disclaimer in the
   12.15 - * documentation and/or other materials provided with the distribution.
   12.16 - *
   12.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   12.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   12.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   12.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   12.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   12.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   12.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   12.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   12.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   12.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   12.27 - *
   12.28 - */
   12.29 -
   12.30 -package de.uapcore.lightpit.dao
   12.31 -
   12.32 -import de.uapcore.lightpit.entities.Component
   12.33 -import de.uapcore.lightpit.entities.Project
   12.34 -
   12.35 -abstract class AbstractComponentDao : AbstractChildEntityDao<Component, Project>() {
   12.36 -    abstract fun findByNode(parent: Project, node: String): Component?
   12.37 -}
   12.38 \ No newline at end of file
    13.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractDao.kt	Sun Dec 20 11:06:25 2020 +0100
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,65 +0,0 @@
    13.4 -/*
    13.5 - * Copyright 2020 Mike Becker. All rights reserved.
    13.6 - *
    13.7 - * Redistribution and use in source and binary forms, with or without
    13.8 - * modification, are permitted provided that the following conditions are met:
    13.9 - *
   13.10 - * 1. Redistributions of source code must retain the above copyright
   13.11 - * notice, this list of conditions and the following disclaimer.
   13.12 - *
   13.13 - * 2. Redistributions in binary form must reproduce the above copyright
   13.14 - * notice, this list of conditions and the following disclaimer in the
   13.15 - * documentation and/or other materials provided with the distribution.
   13.16 - *
   13.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   13.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   13.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   13.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   13.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   13.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   13.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   13.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   13.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   13.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   13.27 - *
   13.28 - */
   13.29 -
   13.30 -package de.uapcore.lightpit.dao
   13.31 -
   13.32 -import java.sql.PreparedStatement
   13.33 -import java.sql.ResultSet
   13.34 -import java.sql.Types
   13.35 -
   13.36 -abstract class AbstractDao<T> {
   13.37 -
   13.38 -    abstract fun mapResult(rs: ResultSet): T
   13.39 -
   13.40 -    protected fun list(stmt: PreparedStatement): List<T> {
   13.41 -        return sequence {
   13.42 -            stmt.executeQuery().use { result ->
   13.43 -                while (result.next()) yield(mapResult(result))
   13.44 -            }
   13.45 -        }.toList()
   13.46 -    }
   13.47 -
   13.48 -    protected fun find(stmt: PreparedStatement): T? {
   13.49 -        stmt.executeQuery().use { result ->
   13.50 -            return if (result.next()) {
   13.51 -                mapResult(result)
   13.52 -            } else {
   13.53 -                null
   13.54 -            }
   13.55 -        }
   13.56 -    }
   13.57 -
   13.58 -    // TODO: create PreparedStatement abstraction that provides some features
   13.59 -
   13.60 -    // TODO: remove the following legacy code helper function
   13.61 -    protected fun <T> setForeignKeyOrNull(stmt: PreparedStatement, index: Int, instance: T?, keyGetter: (obj: T) -> Int) {
   13.62 -        if (instance == null) {
   13.63 -            stmt.setNull(index, Types.INTEGER)
   13.64 -        } else {
   13.65 -            stmt.setInt(index, keyGetter(instance))
   13.66 -        }
   13.67 -    }
   13.68 -}
   13.69 \ No newline at end of file
    14.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractEntityDao.kt	Sun Dec 20 11:06:25 2020 +0100
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,61 +0,0 @@
    14.4 -/*
    14.5 - * Copyright 2020 Mike Becker. All rights reserved.
    14.6 - *
    14.7 - * Redistribution and use in source and binary forms, with or without
    14.8 - * modification, are permitted provided that the following conditions are met:
    14.9 - *
   14.10 - * 1. Redistributions of source code must retain the above copyright
   14.11 - * notice, this list of conditions and the following disclaimer.
   14.12 - *
   14.13 - * 2. Redistributions in binary form must reproduce the above copyright
   14.14 - * notice, this list of conditions and the following disclaimer in the
   14.15 - * documentation and/or other materials provided with the distribution.
   14.16 - *
   14.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   14.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   14.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   14.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   14.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   14.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   14.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   14.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   14.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   14.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   14.27 - *
   14.28 - */
   14.29 -
   14.30 -package de.uapcore.lightpit.dao
   14.31 -
   14.32 -abstract class AbstractEntityDao<T> : AbstractDao<T>() {
   14.33 -
   14.34 -    /**
   14.35 -     * Lists all entities.
   14.36 -     * @return a list of all entities
   14.37 -     */
   14.38 -    abstract fun list(): List<T>
   14.39 -
   14.40 -    /**
   14.41 -     * Finds an entity by its integer ID.
   14.42 -     * It is not guaranteed that referenced entities are automatically joined.
   14.43 -     *
   14.44 -     * @param id the id
   14.45 -     * @return the entity or null if there is no such entity
   14.46 -     */
   14.47 -    abstract fun find(id: Int): T?
   14.48 -
   14.49 -    /**
   14.50 -     * Inserts an instance into database.
   14.51 -     * It is not guaranteed that generated fields will be updated in the instance.
   14.52 -     *
   14.53 -     * @param instance the instance to insert
   14.54 -     */
   14.55 -    abstract fun save(instance: T)
   14.56 -
   14.57 -    /**
   14.58 -     * Updates an instance in the database.
   14.59 -     *
   14.60 -     * @param instance the instance to update
   14.61 -     * @return true if an instance has been updated, false if the instance is not present in database
   14.62 -     */
   14.63 -    abstract fun update(instance: T): Boolean
   14.64 -}
   14.65 \ No newline at end of file
    15.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractIssueDao.kt	Sun Dec 20 11:06:25 2020 +0100
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,100 +0,0 @@
    15.4 -/*
    15.5 - * Copyright 2020 Mike Becker. All rights reserved.
    15.6 - *
    15.7 - * Redistribution and use in source and binary forms, with or without
    15.8 - * modification, are permitted provided that the following conditions are met:
    15.9 - *
   15.10 - * 1. Redistributions of source code must retain the above copyright
   15.11 - * notice, this list of conditions and the following disclaimer.
   15.12 - *
   15.13 - * 2. Redistributions in binary form must reproduce the above copyright
   15.14 - * notice, this list of conditions and the following disclaimer in the
   15.15 - * documentation and/or other materials provided with the distribution.
   15.16 - *
   15.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   15.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   15.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   15.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   15.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   15.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   15.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   15.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   15.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   15.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   15.27 - *
   15.28 - */
   15.29 -
   15.30 -package de.uapcore.lightpit.dao
   15.31 -
   15.32 -import de.uapcore.lightpit.entities.*
   15.33 -import java.sql.SQLException
   15.34 -
   15.35 -abstract class AbstractIssueDao : AbstractChildEntityDao<Issue, Project>() {
   15.36 -
   15.37 -    /**
   15.38 -     * Lists all issues that are related to the specified component and version.
   15.39 -     * If component or version is null, search for issues that are not assigned to any
   15.40 -     * component or version, respectively.
   15.41 -     *
   15.42 -     * @param project the project
   15.43 -     * @param component the component
   15.44 -     * @param version the version
   15.45 -     * @return a list of issues
   15.46 -     */
   15.47 -    abstract fun list(project: Project, component: Component?, version: Version?): List<Issue>
   15.48 -
   15.49 -    /**
   15.50 -     * Lists all issues that are related to the specified version.
   15.51 -     * If the version is null, lists issues that are not assigned to any version.
   15.52 -     *
   15.53 -     * @param project the project
   15.54 -     * @param version the version or null
   15.55 -     * @return a list of issues
   15.56 -     */
   15.57 -    abstract fun list(project: Project, version: Version?): List<Issue>
   15.58 -
   15.59 -    /**
   15.60 -     * Lists all issues that are related to the specified component.
   15.61 -     * If the component is null, lists issues that are not assigned to a component.
   15.62 -     *
   15.63 -     * @param project the project
   15.64 -     * @param component the component or null
   15.65 -     * @return a list of issues
   15.66 -     */
   15.67 -    abstract fun list(project: Project, component: Component?): List<Issue>
   15.68 -
   15.69 -    /**
   15.70 -     * Lists all comments for a specific issue in chronological order.
   15.71 -     *
   15.72 -     * @param issue the issue
   15.73 -     * @return the list of comments
   15.74 -     */
   15.75 -    abstract fun listComments(issue: Issue): List<IssueComment>
   15.76 -
   15.77 -    /**
   15.78 -     * Stores the specified comment in database.
   15.79 -     * This is an update-or-insert operation.
   15.80 -     * The "updated" date of the corresponding issue is also updated.
   15.81 -     *
   15.82 -     * @param issue the issue to save the comment for
   15.83 -     * @param comment the comment to save
   15.84 -     */
   15.85 -    abstract fun saveComment(issue: Issue, comment: IssueComment)
   15.86 -
   15.87 -    /**
   15.88 -     * Saves an instances to the database.
   15.89 -     * Implementations of this DAO must guarantee that the generated ID is stored in the instance.
   15.90 -     *
   15.91 -     * @param instance the instance to insert
   15.92 -     * @param parent the parent project
   15.93 -     * @throws SQLException on any kind of SQL error
   15.94 -     */
   15.95 -    abstract override fun save(instance: Issue, parent: Project)
   15.96 -
   15.97 -    /**
   15.98 -     * Retrieves the affected, scheduled and resolved versions for the specified issue.
   15.99 -     *
  15.100 -     * @param issue the issue to join the information for
  15.101 -     */
  15.102 -    abstract fun joinVersionInformation(issue: Issue)
  15.103 -}
  15.104 \ No newline at end of file
    16.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractProjectDao.kt	Sun Dec 20 11:06:25 2020 +0100
    16.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3 @@ -1,37 +0,0 @@
    16.4 -/*
    16.5 - * Copyright 2020 Mike Becker. All rights reserved.
    16.6 - *
    16.7 - * Redistribution and use in source and binary forms, with or without
    16.8 - * modification, are permitted provided that the following conditions are met:
    16.9 - *
   16.10 - * 1. Redistributions of source code must retain the above copyright
   16.11 - * notice, this list of conditions and the following disclaimer.
   16.12 - *
   16.13 - * 2. Redistributions in binary form must reproduce the above copyright
   16.14 - * notice, this list of conditions and the following disclaimer in the
   16.15 - * documentation and/or other materials provided with the distribution.
   16.16 - *
   16.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   16.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   16.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   16.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   16.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   16.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   16.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   16.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   16.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   16.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   16.27 - *
   16.28 - */
   16.29 -
   16.30 -package de.uapcore.lightpit.dao
   16.31 -
   16.32 -import de.uapcore.lightpit.entities.IssueSummary
   16.33 -import de.uapcore.lightpit.entities.Project
   16.34 -
   16.35 -abstract class AbstractProjectDao : AbstractEntityDao<Project>() {
   16.36 -
   16.37 -    abstract fun getIssueSummary(project: Project): IssueSummary
   16.38 -
   16.39 -    abstract fun findByNode(node: String): Project?
   16.40 -}
   16.41 \ No newline at end of file
    17.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractUserDao.kt	Sun Dec 20 11:06:25 2020 +0100
    17.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3 @@ -1,33 +0,0 @@
    17.4 -/*
    17.5 - * Copyright 2020 Mike Becker. All rights reserved.
    17.6 - *
    17.7 - * Redistribution and use in source and binary forms, with or without
    17.8 - * modification, are permitted provided that the following conditions are met:
    17.9 - *
   17.10 - * 1. Redistributions of source code must retain the above copyright
   17.11 - * notice, this list of conditions and the following disclaimer.
   17.12 - *
   17.13 - * 2. Redistributions in binary form must reproduce the above copyright
   17.14 - * notice, this list of conditions and the following disclaimer in the
   17.15 - * documentation and/or other materials provided with the distribution.
   17.16 - *
   17.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   17.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   17.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   17.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   17.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   17.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   17.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   17.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   17.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   17.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   17.27 - *
   17.28 - */
   17.29 -
   17.30 -package de.uapcore.lightpit.dao
   17.31 -
   17.32 -import de.uapcore.lightpit.entities.User
   17.33 -
   17.34 -abstract class AbstractUserDao : AbstractEntityDao<User>() {
   17.35 -    abstract fun findByUsername(username: String): User?
   17.36 -}
   17.37 \ No newline at end of file
    18.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractVersionDao.kt	Sun Dec 20 11:06:25 2020 +0100
    18.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.3 @@ -1,34 +0,0 @@
    18.4 -/*
    18.5 - * Copyright 2020 Mike Becker. All rights reserved.
    18.6 - *
    18.7 - * Redistribution and use in source and binary forms, with or without
    18.8 - * modification, are permitted provided that the following conditions are met:
    18.9 - *
   18.10 - * 1. Redistributions of source code must retain the above copyright
   18.11 - * notice, this list of conditions and the following disclaimer.
   18.12 - *
   18.13 - * 2. Redistributions in binary form must reproduce the above copyright
   18.14 - * notice, this list of conditions and the following disclaimer in the
   18.15 - * documentation and/or other materials provided with the distribution.
   18.16 - *
   18.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   18.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   18.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   18.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   18.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   18.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   18.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   18.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   18.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   18.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   18.27 - *
   18.28 - */
   18.29 -
   18.30 -package de.uapcore.lightpit.dao
   18.31 -
   18.32 -import de.uapcore.lightpit.entities.Project
   18.33 -import de.uapcore.lightpit.entities.Version
   18.34 -
   18.35 -abstract class AbstractVersionDao : AbstractChildEntityDao<Version, Project>() {
   18.36 -    abstract fun findByNode(parent: Project, node: String): Version?
   18.37 -}
   18.38 \ No newline at end of file
    19.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DaoProvider.kt	Sun Dec 20 11:06:25 2020 +0100
    19.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.3 @@ -1,35 +0,0 @@
    19.4 -/*
    19.5 - * Copyright 2020 Mike Becker. All rights reserved.
    19.6 - *
    19.7 - * Redistribution and use in source and binary forms, with or without
    19.8 - * modification, are permitted provided that the following conditions are met:
    19.9 - *
   19.10 - * 1. Redistributions of source code must retain the above copyright
   19.11 - * notice, this list of conditions and the following disclaimer.
   19.12 - *
   19.13 - * 2. Redistributions in binary form must reproduce the above copyright
   19.14 - * notice, this list of conditions and the following disclaimer in the
   19.15 - * documentation and/or other materials provided with the distribution.
   19.16 - *
   19.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   19.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   19.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   19.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   19.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   19.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   19.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   19.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   19.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   19.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   19.27 - *
   19.28 - */
   19.29 -
   19.30 -package de.uapcore.lightpit.dao
   19.31 -
   19.32 -interface DaoProvider {
   19.33 -    val userDao: AbstractUserDao
   19.34 -    val projectDao: AbstractProjectDao
   19.35 -    val componentDao: AbstractComponentDao
   19.36 -    val versionDao: AbstractVersionDao
   19.37 -    val issueDao: AbstractIssueDao
   19.38 -}
   19.39 \ No newline at end of file
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Mon Dec 21 18:29:34 2020 +0100
    20.3 @@ -0,0 +1,65 @@
    20.4 +/*
    20.5 + * Copyright 2020 Mike Becker. All rights reserved.
    20.6 + *
    20.7 + * Redistribution and use in source and binary forms, with or without
    20.8 + * modification, are permitted provided that the following conditions are met:
    20.9 + *
   20.10 + * 1. Redistributions of source code must retain the above copyright
   20.11 + * notice, this list of conditions and the following disclaimer.
   20.12 + *
   20.13 + * 2. Redistributions in binary form must reproduce the above copyright
   20.14 + * notice, this list of conditions and the following disclaimer in the
   20.15 + * documentation and/or other materials provided with the distribution.
   20.16 + *
   20.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   20.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   20.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   20.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   20.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   20.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   20.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   20.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   20.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   20.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   20.27 + */
   20.28 +
   20.29 +package de.uapcore.lightpit.dao
   20.30 +
   20.31 +import de.uapcore.lightpit.entities.*
   20.32 +import de.uapcore.lightpit.filter.IssueFilter
   20.33 +
   20.34 +interface DataAccessObject {
   20.35 +    fun listUsers(): List<User>
   20.36 +    fun findUser(id: Int): User?
   20.37 +    fun findUserByName(username: String): User?
   20.38 +    fun insertUser(user: User)
   20.39 +    fun updateUser(user: User)
   20.40 +
   20.41 +    fun listVersions(project: Project): List<Version>
   20.42 +    fun findVersion(id: Int): Version?
   20.43 +    fun findVersionByNode(project: Project, node: String): Version?
   20.44 +    fun insertVersion(version: Version)
   20.45 +    fun updateVersion(version: Version)
   20.46 +
   20.47 +    fun listComponents(project: Project): List<Component>
   20.48 +    fun findComponent(id: Int): Component?
   20.49 +    fun findComponentByNode(project: Project, node: String): Component?
   20.50 +    fun insertComponent(component: Component)
   20.51 +    fun updateComponent(component: Component)
   20.52 +
   20.53 +    fun listProjects(): List<Project>
   20.54 +    fun findProject(id: Int): Project?
   20.55 +    fun findProjectByNode(node: String): Project?
   20.56 +    fun insertProject(project: Project)
   20.57 +    fun updateProject(project: Project)
   20.58 +
   20.59 +    fun collectIssueSummary(project: Project): IssueSummary
   20.60 +
   20.61 +    fun listIssues(filter: IssueFilter): List<Issue>
   20.62 +    fun findIssue(id: Int): Issue?
   20.63 +    fun insertIssue(issue: Issue)
   20.64 +    fun updateIssue(issue: Issue)
   20.65 +
   20.66 +    fun listComments(issue: Issue): List<IssueComment>
   20.67 +    fun insertComment(issueComment: IssueComment)
   20.68 +}
   20.69 \ No newline at end of file
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/Extensions.kt	Mon Dec 21 18:29:34 2020 +0100
    21.3 @@ -0,0 +1,64 @@
    21.4 +/*
    21.5 + * Copyright 2020 Mike Becker. All rights reserved.
    21.6 + *
    21.7 + * Redistribution and use in source and binary forms, with or without
    21.8 + * modification, are permitted provided that the following conditions are met:
    21.9 + *
   21.10 + * 1. Redistributions of source code must retain the above copyright
   21.11 + * notice, this list of conditions and the following disclaimer.
   21.12 + *
   21.13 + * 2. Redistributions in binary form must reproduce the above copyright
   21.14 + * notice, this list of conditions and the following disclaimer in the
   21.15 + * documentation and/or other materials provided with the distribution.
   21.16 + *
   21.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   21.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   21.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   21.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   21.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   21.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   21.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   21.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   21.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   21.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   21.27 + */
   21.28 +
   21.29 +package de.uapcore.lightpit.dao
   21.30 +
   21.31 +import java.sql.Date
   21.32 +import java.sql.PreparedStatement
   21.33 +import java.sql.ResultSet
   21.34 +import java.sql.Types
   21.35 +
   21.36 +fun PreparedStatement.setStringSafe(idx: Int, str: String) {
   21.37 +    setString(idx, str)
   21.38 +}
   21.39 +
   21.40 +fun PreparedStatement.setStringOrNull(idx: Int, str: String?) {
   21.41 +    when (str) {
   21.42 +        null -> setNull(idx, Types.VARCHAR)
   21.43 +        else -> setString(idx, str)
   21.44 +    }
   21.45 +}
   21.46 +
   21.47 +fun PreparedStatement.setIntOrNull(idx: Int, value: Int?) {
   21.48 +    when (value) {
   21.49 +        null -> setNull(idx, Types.INTEGER)
   21.50 +        else -> setInt(idx, value)
   21.51 +    }
   21.52 +}
   21.53 +
   21.54 +fun PreparedStatement.setDateOrNull(idx: Int, value: Date?) {
   21.55 +    when (value) {
   21.56 +        null -> setNull(idx, Types.DATE)
   21.57 +        else -> setDate(idx, value)
   21.58 +    }
   21.59 +}
   21.60 +
   21.61 +fun <T : Enum<T>> PreparedStatement.setEnum(idx: Int, e: Enum<T>) {
   21.62 +    setString(idx, e.name)
   21.63 +}
   21.64 +
   21.65 +inline fun <reified T : Enum<T>> ResultSet.getEnum(col: String): T {
   21.66 +    return enumValueOf(getString(col))
   21.67 +}
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Mon Dec 21 18:29:34 2020 +0100
    22.3 @@ -0,0 +1,739 @@
    22.4 +/*
    22.5 + * Copyright 2020 Mike Becker. All rights reserved.
    22.6 + *
    22.7 + * Redistribution and use in source and binary forms, with or without
    22.8 + * modification, are permitted provided that the following conditions are met:
    22.9 + *
   22.10 + * 1. Redistributions of source code must retain the above copyright
   22.11 + * notice, this list of conditions and the following disclaimer.
   22.12 + *
   22.13 + * 2. Redistributions in binary form must reproduce the above copyright
   22.14 + * notice, this list of conditions and the following disclaimer in the
   22.15 + * documentation and/or other materials provided with the distribution.
   22.16 + *
   22.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   22.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   22.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   22.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   22.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   22.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   22.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   22.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   22.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   22.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   22.27 + */
   22.28 +
   22.29 +package de.uapcore.lightpit.dao
   22.30 +
   22.31 +import de.uapcore.lightpit.entities.*
   22.32 +import de.uapcore.lightpit.filter.*
   22.33 +import de.uapcore.lightpit.types.WebColor
   22.34 +import java.sql.Connection
   22.35 +import java.sql.PreparedStatement
   22.36 +import java.sql.ResultSet
   22.37 +
   22.38 +class PostgresDataAccessObject(private val connection: Connection) : DataAccessObject {
   22.39 +
   22.40 +    //<editor-fold desc="User">
   22.41 +    private fun selectUserInfo(
   22.42 +        rs: ResultSet,
   22.43 +        idColumn: String = "userid",
   22.44 +        usernameColumn: String = "username",
   22.45 +        givennameColumn: String = "givenname",
   22.46 +        lastnameColumn: String = "lastname",
   22.47 +        mailColumn: String = "mail"
   22.48 +    ): User? {
   22.49 +        val idval = rs.getInt(idColumn)
   22.50 +        return if (rs.wasNull()) null else {
   22.51 +            User(idval).apply {
   22.52 +                username = rs.getString(usernameColumn)
   22.53 +                givenname = rs.getString(givennameColumn)
   22.54 +                lastname = rs.getString(lastnameColumn)
   22.55 +                mail = rs.getString(mailColumn)
   22.56 +            }
   22.57 +        }
   22.58 +    }
   22.59 +
   22.60 +    private fun selectUsers(stmt: PreparedStatement) = sequence {
   22.61 +        stmt.executeQuery().use { rs ->
   22.62 +            while (rs.next()) selectUserInfo(rs)?.let { yield(it) }
   22.63 +        }
   22.64 +    }
   22.65 +
   22.66 +    //language=SQL
   22.67 +    private val userQuery = "select userid, username, lastname, givenname, mail from lpit_user"
   22.68 +
   22.69 +    private val stmtUsers by lazy {
   22.70 +        connection.prepareStatement(
   22.71 +            """${userQuery}
   22.72 +            where userid > 0
   22.73 +            order by username
   22.74 +            """
   22.75 +        )
   22.76 +    }
   22.77 +    private val stmtUserByID by lazy {
   22.78 +        connection.prepareStatement(
   22.79 +            """${userQuery}
   22.80 +            where userid = ?
   22.81 +            """
   22.82 +        )
   22.83 +    }
   22.84 +    private val stmtUserByName by lazy {
   22.85 +        connection.prepareStatement(
   22.86 +            """${userQuery}
   22.87 +            where lower(username) = lower(?)
   22.88 +            """
   22.89 +        )
   22.90 +    }
   22.91 +    private val stmtInsertUser by lazy {
   22.92 +        connection.prepareStatement(
   22.93 +            "insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)"
   22.94 +        )
   22.95 +    }
   22.96 +    private val stmtUpdateUser by lazy {
   22.97 +        connection.prepareStatement(
   22.98 +            "update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?"
   22.99 +        )
  22.100 +    }
  22.101 +
  22.102 +    override fun listUsers() = selectUsers(stmtUsers).toList()
  22.103 +    override fun findUser(id: Int): User? {
  22.104 +        stmtUserByID.setInt(1, id)
  22.105 +        return selectUsers(stmtUserByID).firstOrNull()
  22.106 +    }
  22.107 +
  22.108 +    override fun findUserByName(username: String): User? {
  22.109 +        stmtUserByName.setString(1, username)
  22.110 +        return selectUsers(stmtUserByName).firstOrNull()
  22.111 +    }
  22.112 +
  22.113 +    override fun insertUser(user: User) {
  22.114 +        with(user) {
  22.115 +            stmtInsertUser.setStringSafe(1, username)
  22.116 +            stmtInsertUser.setStringOrNull(2, lastname)
  22.117 +            stmtInsertUser.setStringOrNull(3, givenname)
  22.118 +            stmtInsertUser.setStringOrNull(4, mail)
  22.119 +        }
  22.120 +        stmtInsertUser.execute()
  22.121 +    }
  22.122 +
  22.123 +    override fun updateUser(user: User) {
  22.124 +        with(user) {
  22.125 +            stmtUpdateUser.setStringOrNull(1, lastname)
  22.126 +            stmtUpdateUser.setStringOrNull(2, givenname)
  22.127 +            stmtUpdateUser.setStringOrNull(3, mail)
  22.128 +            stmtUpdateUser.setInt(4, id)
  22.129 +        }
  22.130 +        stmtUpdateUser.execute()
  22.131 +    }
  22.132 +    //</editor-fold>
  22.133 +
  22.134 +    //<editor-fold desc="Version">
  22.135 +    private fun selectVersions(stmt: PreparedStatement) = sequence {
  22.136 +        stmt.executeQuery().use { rs ->
  22.137 +            while (rs.next()) {
  22.138 +                yield(Version(rs.getInt("versionid"), rs.getInt("project")).apply {
  22.139 +                    name = rs.getString("name")
  22.140 +                    node = rs.getString("node")
  22.141 +                    ordinal = rs.getInt("ordinal")
  22.142 +                    status = rs.getEnum("status")
  22.143 +                })
  22.144 +            }
  22.145 +        }
  22.146 +    }
  22.147 +
  22.148 +    private fun setVersionFields(stmt: PreparedStatement, obj: Version): Int {
  22.149 +        with(obj) {
  22.150 +            stmt.setStringSafe(1, name)
  22.151 +            stmt.setStringSafe(2, node)
  22.152 +            stmt.setInt(3, ordinal)
  22.153 +            stmt.setEnum(4, status)
  22.154 +        }
  22.155 +        return 5
  22.156 +    }
  22.157 +
  22.158 +    //language=SQL
  22.159 +    private val versionQuery = "select versionid, project, name, node, ordinal, status from lpit_version"
  22.160 +
  22.161 +    private val stmtVersions by lazy {
  22.162 +        connection.prepareStatement(
  22.163 +            """${versionQuery}
  22.164 +            where project = ?
  22.165 +            order by ordinal desc, lower(name) desc
  22.166 +            """
  22.167 +        )
  22.168 +    }
  22.169 +    private val stmtVersionByID by lazy {
  22.170 +        connection.prepareStatement(
  22.171 +            """${versionQuery}
  22.172 +            where versionid = ?
  22.173 +            """
  22.174 +        )
  22.175 +    }
  22.176 +    private val stmtVersionByNode by lazy {
  22.177 +        connection.prepareStatement(
  22.178 +            """${versionQuery}
  22.179 +            where project = ? and node = ?
  22.180 +            """
  22.181 +        )
  22.182 +    }
  22.183 +    private val stmtInsertVersion by lazy {
  22.184 +        connection.prepareStatement(
  22.185 +            """
  22.186 +            insert into lpit_version (name, node, ordinal, status, project)
  22.187 +            values (?, ?, ?, ?::version_status, ?)
  22.188 +            """
  22.189 +        )
  22.190 +    }
  22.191 +    private val stmtUpdateVersion by lazy {
  22.192 +        connection.prepareStatement(
  22.193 +            """
  22.194 +            update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status
  22.195 +            where versionid = ?
  22.196 +            """
  22.197 +        )
  22.198 +    }
  22.199 +
  22.200 +    override fun listVersions(project: Project): List<Version> {
  22.201 +        stmtVersions.setInt(1, project.id)
  22.202 +        return selectVersions(stmtVersions).toList()
  22.203 +    }
  22.204 +
  22.205 +    override fun findVersion(id: Int): Version? {
  22.206 +        stmtVersionByID.setInt(1, id)
  22.207 +        return selectVersions(stmtVersionByID).firstOrNull()
  22.208 +    }
  22.209 +
  22.210 +    override fun findVersionByNode(project: Project, node: String): Version? {
  22.211 +        stmtVersionByNode.setInt(1, project.id)
  22.212 +        stmtVersionByNode.setString(2, node)
  22.213 +        return selectVersions(stmtVersionByNode).firstOrNull()
  22.214 +    }
  22.215 +
  22.216 +    override fun insertVersion(version: Version) {
  22.217 +        val col = setVersionFields(stmtInsertVersion, version)
  22.218 +        stmtInsertVersion.setInt(col, version.projectid)
  22.219 +        stmtInsertVersion.execute()
  22.220 +    }
  22.221 +
  22.222 +    override fun updateVersion(version: Version) {
  22.223 +        val col = setVersionFields(stmtUpdateVersion, version)
  22.224 +        stmtUpdateVersion.setInt(col, version.id)
  22.225 +        stmtUpdateVersion.execute()
  22.226 +    }
  22.227 +    //</editor-fold>
  22.228 +
  22.229 +    //<editor-fold desc="Component">
  22.230 +    private fun selectComponents(stmt: PreparedStatement) = sequence {
  22.231 +        stmt.executeQuery().use { rs ->
  22.232 +            while (rs.next()) {
  22.233 +                yield(Component(rs.getInt("id"), rs.getInt("project")).apply {
  22.234 +                    name = rs.getString("name")
  22.235 +                    node = rs.getString("node")
  22.236 +                    color = try {
  22.237 +                        WebColor(rs.getString("color"))
  22.238 +                    } catch (ex: IllegalArgumentException) {
  22.239 +                        WebColor("000000")
  22.240 +                    }
  22.241 +                    ordinal = rs.getInt("ordinal")
  22.242 +                    description = rs.getString("description")
  22.243 +                    lead = selectUserInfo(rs)
  22.244 +                })
  22.245 +            }
  22.246 +        }
  22.247 +    }
  22.248 +
  22.249 +    private fun setComponentFields(stmt: PreparedStatement, obj: Component): Int {
  22.250 +        with(obj) {
  22.251 +            stmt.setStringSafe(1, name)
  22.252 +            stmt.setStringSafe(2, node)
  22.253 +            stmt.setStringSafe(3, color.hex)
  22.254 +            stmt.setInt(4, ordinal)
  22.255 +            stmt.setStringOrNull(5, description)
  22.256 +            stmt.setIntOrNull(6, obj.lead?.id)
  22.257 +        }
  22.258 +        return 7
  22.259 +    }
  22.260 +
  22.261 +    //language=SQL
  22.262 +    private val componentQuery =
  22.263 +        """
  22.264 +        select id, project, name, node, color, ordinal, description,
  22.265 +            userid, username, givenname, lastname, mail
  22.266 +        from lpit_component
  22.267 +        left join lpit_user on lead = userid
  22.268 +        """
  22.269 +
  22.270 +    private val stmtComponents by lazy {
  22.271 +        connection.prepareStatement(
  22.272 +            """${componentQuery}
  22.273 +            where project = ?
  22.274 +            order by ordinal, lower(name)
  22.275 +            """
  22.276 +        )
  22.277 +    }
  22.278 +    private val stmtComponentById by lazy {
  22.279 +        connection.prepareStatement(
  22.280 +            """${componentQuery}
  22.281 +            where id = ?
  22.282 +            """
  22.283 +        )
  22.284 +    }
  22.285 +    private val stmtComponentByNode by lazy {
  22.286 +        connection.prepareStatement(
  22.287 +            """${componentQuery}
  22.288 +            where project = ? and node = ?
  22.289 +            """
  22.290 +        )
  22.291 +    }
  22.292 +    private val stmtInsertComponent by lazy {
  22.293 +        connection.prepareStatement(
  22.294 +            """
  22.295 +            insert into lpit_component (name, node, color, ordinal, description, lead, project)
  22.296 +            values (?, ?, ?, ?, ?, ?, ?)
  22.297 +            """
  22.298 +        )
  22.299 +    }
  22.300 +    private val stmtUpdateComponent by lazy {
  22.301 +        connection.prepareStatement(
  22.302 +            "update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?"
  22.303 +        )
  22.304 +    }
  22.305 +
  22.306 +    override fun listComponents(project: Project): List<Component> {
  22.307 +        stmtComponents.setInt(1, project.id)
  22.308 +        return selectComponents(stmtComponents).toList()
  22.309 +    }
  22.310 +
  22.311 +    override fun findComponent(id: Int): Component? {
  22.312 +        stmtComponentById.setInt(1, id)
  22.313 +        return selectComponents(stmtComponentById).firstOrNull()
  22.314 +    }
  22.315 +
  22.316 +    override fun findComponentByNode(project: Project, node: String): Component? {
  22.317 +        stmtComponentByNode.setInt(1, project.id)
  22.318 +        stmtComponentByNode.setString(2, node)
  22.319 +        return selectComponents(stmtComponentByNode).firstOrNull()
  22.320 +    }
  22.321 +
  22.322 +    override fun insertComponent(component: Component) {
  22.323 +        val col = setComponentFields(stmtInsertComponent, component)
  22.324 +        stmtInsertComponent.setInt(col, component.projectid)
  22.325 +        stmtInsertComponent.execute()
  22.326 +    }
  22.327 +
  22.328 +    override fun updateComponent(component: Component) {
  22.329 +        val col = setComponentFields(stmtUpdateComponent, component)
  22.330 +        stmtUpdateComponent.setInt(col, component.id)
  22.331 +        stmtUpdateComponent.execute()
  22.332 +    }
  22.333 +
  22.334 +    //</editor-fold>
  22.335 +
  22.336 +    //<editor-fold desc="Project">
  22.337 +
  22.338 +    private fun selectProjects(stmt: PreparedStatement) = sequence {
  22.339 +        stmt.executeQuery().use { rs ->
  22.340 +            while (rs.next()) {
  22.341 +                yield(Project(rs.getInt("projectid")).apply {
  22.342 +                    name = rs.getString("name")
  22.343 +                    node = rs.getString("node")
  22.344 +                    description = rs.getString("description")
  22.345 +                    repoUrl = rs.getString("repourl")
  22.346 +                    owner = selectUserInfo(rs)
  22.347 +                })
  22.348 +            }
  22.349 +        }
  22.350 +    }
  22.351 +
  22.352 +    private fun setProjectFields(stmt: PreparedStatement, obj: Project): Int {
  22.353 +        with(obj) {
  22.354 +            stmt.setStringSafe(1, name)
  22.355 +            stmt.setStringSafe(2, node)
  22.356 +            stmt.setStringOrNull(3, description)
  22.357 +            stmt.setStringOrNull(4, repoUrl)
  22.358 +            stmt.setIntOrNull(5, owner?.id)
  22.359 +        }
  22.360 +        return 6
  22.361 +    }
  22.362 +
  22.363 +    //language=SQL
  22.364 +    private val projectQuery =
  22.365 +        """
  22.366 +        select projectid, name, node, description, repourl,
  22.367 +            userid, username, lastname, givenname, mail
  22.368 +        from lpit_project
  22.369 +        left join lpit_user owner on lpit_project.owner = owner.userid
  22.370 +        """
  22.371 +
  22.372 +    private val stmtProjects by lazy {
  22.373 +        connection.prepareStatement(
  22.374 +            """${projectQuery}
  22.375 +            order by lower(name)
  22.376 +            """
  22.377 +        )
  22.378 +    }
  22.379 +    private val stmtProjectByID by lazy {
  22.380 +        connection.prepareStatement(
  22.381 +            """${projectQuery}
  22.382 +            where projectid = ?
  22.383 +            """
  22.384 +        )
  22.385 +    }
  22.386 +    private val stmtProjectByNode by lazy {
  22.387 +        connection.prepareStatement(
  22.388 +            """${projectQuery}
  22.389 +            where node = ?
  22.390 +            """
  22.391 +        )
  22.392 +    }
  22.393 +    private val stmtInsertProject by lazy {
  22.394 +        connection.prepareStatement(
  22.395 +            "insert into lpit_project (name, node, description, repourl, owner) values (?, ?, ?, ?, ?)"
  22.396 +        )
  22.397 +    }
  22.398 +    private val stmtUpdateProject by lazy {
  22.399 +        connection.prepareStatement(
  22.400 +            "update lpit_project set name = ?, node = ?, description = ?, repourl = ?, owner = ? where projectid = ?"
  22.401 +        )
  22.402 +    }
  22.403 +    private val stmtIssueSummary by lazy {
  22.404 +        connection.prepareStatement(
  22.405 +            """
  22.406 +            select phase, count(*) as total
  22.407 +            from lpit_issue
  22.408 +            join lpit_issue_phases using(status)
  22.409 +            where project = ?
  22.410 +            group by phase  
  22.411 +            """
  22.412 +        )
  22.413 +    }
  22.414 +
  22.415 +    override fun listProjects(): List<Project> {
  22.416 +        return selectProjects(stmtProjects).toList()
  22.417 +    }
  22.418 +
  22.419 +    override fun findProject(id: Int): Project? {
  22.420 +        stmtProjectByID.setInt(1, id)
  22.421 +        return selectProjects(stmtProjectByID).firstOrNull()
  22.422 +    }
  22.423 +
  22.424 +    override fun findProjectByNode(node: String): Project? {
  22.425 +        stmtProjectByNode.setString(1, node)
  22.426 +        return selectProjects(stmtProjectByNode).firstOrNull()
  22.427 +    }
  22.428 +
  22.429 +    override fun insertProject(project: Project) {
  22.430 +        setProjectFields(stmtInsertProject, project)
  22.431 +        stmtInsertProject.execute()
  22.432 +    }
  22.433 +
  22.434 +    override fun updateProject(project: Project) {
  22.435 +        val col = setProjectFields(stmtUpdateProject, project)
  22.436 +        stmtUpdateProject.setInt(col, project.id)
  22.437 +        stmtUpdateProject.execute()
  22.438 +    }
  22.439 +
  22.440 +    override fun collectIssueSummary(project: Project): IssueSummary {
  22.441 +        stmtIssueSummary.setInt(1, project.id)
  22.442 +        return stmtIssueSummary.executeQuery().use { rs ->
  22.443 +            val summary = IssueSummary()
  22.444 +            while (rs.next()) {
  22.445 +                val phase = rs.getInt("phase")
  22.446 +                val total = rs.getInt("total")
  22.447 +                when (phase) {
  22.448 +                    0 -> summary.open = total
  22.449 +                    1 -> summary.active = total
  22.450 +                    2 -> summary.done = total
  22.451 +                }
  22.452 +            }
  22.453 +            summary
  22.454 +        }
  22.455 +    }
  22.456 +
  22.457 +    //</editor-fold>
  22.458 +
  22.459 +    //<editor-fold desc="Issue">
  22.460 +
  22.461 +    private fun selectIssues(stmt: PreparedStatement) = sequence {
  22.462 +        stmt.executeQuery().use { rs ->
  22.463 +            while (rs.next()) {
  22.464 +                val proj = Project(rs.getInt("project")).apply {
  22.465 +                    name = rs.getString("projectname")
  22.466 +                    node = rs.getString("projectnode")
  22.467 +                }
  22.468 +                val comp = rs.getInt("component").let {
  22.469 +                    if (rs.wasNull()) null else
  22.470 +                        Component(it, proj.id).apply {
  22.471 +                            name = rs.getString("componentname")
  22.472 +                            node = rs.getString("componentnode")
  22.473 +                        }
  22.474 +                }
  22.475 +                val issue = Issue(rs.getInt("issueid"), proj, comp).apply {
  22.476 +                    component = comp
  22.477 +                    status = rs.getEnum("status")
  22.478 +                    category = rs.getEnum("category")
  22.479 +                    subject = rs.getString("subject")
  22.480 +                    description = rs.getString("description")
  22.481 +                    assignee = selectUserInfo(rs)
  22.482 +                    created = rs.getTimestamp("created")
  22.483 +                    updated = rs.getTimestamp("updated")
  22.484 +                    eta = rs.getDate("eta")
  22.485 +                }
  22.486 +                queryAffectedVersions.setInt(1, issue.id)
  22.487 +                issue.affectedVersions = selectVersions(queryAffectedVersions).toList()
  22.488 +                queryResolvedVersions.setInt(1, issue.id)
  22.489 +                issue.resolvedVersions = selectVersions(queryResolvedVersions).toList()
  22.490 +                yield(issue)
  22.491 +            }
  22.492 +        }
  22.493 +    }
  22.494 +
  22.495 +    private fun setIssueFields(stmt: PreparedStatement, obj: Issue): Int {
  22.496 +        with(obj) {
  22.497 +            stmt.setIntOrNull(1, component?.id)
  22.498 +            stmt.setEnum(2, status)
  22.499 +            stmt.setEnum(3, category)
  22.500 +            stmt.setStringSafe(4, subject)
  22.501 +            stmt.setStringOrNull(5, description)
  22.502 +            stmt.setIntOrNull(6, assignee?.id)
  22.503 +            stmt.setDateOrNull(7, eta)
  22.504 +        }
  22.505 +        return 8
  22.506 +    }
  22.507 +
  22.508 +    //language=SQL
  22.509 +    private val issueQuery =
  22.510 +        """
  22.511 +        select issueid,
  22.512 +            i.project, p.name as projectname, p.node as projectnode,
  22.513 +            component, c.name as componentname, c.node as componentnode,
  22.514 +            status, category, subject, i.description,
  22.515 +            userid, username, givenname, lastname, mail,
  22.516 +            created, updated, eta
  22.517 +        from lpit_issue i
  22.518 +        join lpit_project p on i.project = projectid
  22.519 +        left join lpit_component c on component = c.id
  22.520 +        left join lpit_user on userid = assignee 
  22.521 +        """
  22.522 +
  22.523 +    private val queryResolvedVersions by lazy {
  22.524 +        connection.prepareStatement(
  22.525 +            """
  22.526 +            select versionid, project, name, status, ordinal, node
  22.527 +            from lpit_version v join lpit_issue_resolved_version using (versionid)
  22.528 +            where issueid = ?
  22.529 +            order by ordinal, name
  22.530 +            """
  22.531 +        )
  22.532 +    }
  22.533 +
  22.534 +    private val queryAffectedVersions by lazy {
  22.535 +        connection.prepareStatement(
  22.536 +            """
  22.537 +            select versionid, project, name, status, ordinal, node
  22.538 +            from lpit_version join lpit_issue_affected_version using (versionid)
  22.539 +            where issueid = ?
  22.540 +            order by ordinal, name
  22.541 +            """
  22.542 +        )
  22.543 +    }
  22.544 +
  22.545 +    private val stmtIssues by lazy {
  22.546 +        connection.prepareStatement(
  22.547 +            """
  22.548 +            with issue_version as (
  22.549 +                select issueid, versionid from lpit_issue_affected_version
  22.550 +                union select issueid, versionid from lpit_issue_resolved_version
  22.551 +            ) ${issueQuery} left join issue_version using (issueid)
  22.552 +            where
  22.553 +            (not ? or projectid = ?) and 
  22.554 +            (not ? or versionid = ?) and (not ? or versionid is null) and
  22.555 +            (not ? or component = ?) and (not ? or component is null)
  22.556 +            """
  22.557 +        )
  22.558 +    }
  22.559 +
  22.560 +    private val fproj = 1
  22.561 +    private val projectid = 2
  22.562 +    private val fversion = 3
  22.563 +    private val versionid = 4
  22.564 +    private val nversion = 5
  22.565 +    private val fcomp = 6
  22.566 +    private val component = 7
  22.567 +    private val ncomp = 8
  22.568 +
  22.569 +    private fun <T : Entity> applyFilter(filter: Filter<T>, fflag: Int, nflag: Int, idcol: Int) {
  22.570 +        when (filter) {
  22.571 +            is AllFilter -> {
  22.572 +                stmtIssues.setBoolean(fflag, false)
  22.573 +                stmtIssues.setBoolean(nflag, false)
  22.574 +                stmtIssues.setInt(idcol, 0)
  22.575 +            }
  22.576 +            is NoneFilter -> {
  22.577 +                stmtIssues.setBoolean(fflag, false)
  22.578 +                stmtIssues.setBoolean(nflag, true)
  22.579 +                stmtIssues.setInt(idcol, 0)
  22.580 +            }
  22.581 +            is SpecificFilter -> {
  22.582 +                stmtIssues.setBoolean(fflag, true)
  22.583 +                stmtIssues.setBoolean(nflag, false)
  22.584 +                stmtIssues.setInt(idcol, filter.obj.id)
  22.585 +            }
  22.586 +            else -> {
  22.587 +                TODO("Implement range filter.")
  22.588 +            }
  22.589 +        }
  22.590 +    }
  22.591 +
  22.592 +    override fun listIssues(filter: IssueFilter): List<Issue> {
  22.593 +        when (filter.project) {
  22.594 +            is AllFilter -> {
  22.595 +                stmtIssues.setBoolean(fproj, false)
  22.596 +                stmtIssues.setInt(projectid, 0)
  22.597 +            }
  22.598 +            is SpecificFilter -> {
  22.599 +                stmtIssues.setBoolean(fproj, true)
  22.600 +                stmtIssues.setInt(projectid, filter.project.obj.id)
  22.601 +            }
  22.602 +            else -> throw IllegalArgumentException()
  22.603 +        }
  22.604 +        applyFilter(filter.version, fversion, nversion, versionid)
  22.605 +        applyFilter(filter.component, fcomp, ncomp, component)
  22.606 +
  22.607 +        return selectIssues(stmtIssues).toList()
  22.608 +    }
  22.609 +
  22.610 +    private val stmtFindIssueByID by lazy {
  22.611 +        connection.prepareStatement(
  22.612 +            """${issueQuery}
  22.613 +            where issueid = ?
  22.614 +            """
  22.615 +        )
  22.616 +    }
  22.617 +    private val stmtInsertIssue by lazy {
  22.618 +        connection.prepareStatement(
  22.619 +            """
  22.620 +            insert into lpit_issue (component, status, category, subject, description, assignee, eta, project)
  22.621 +            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?)
  22.622 +            returning issueid
  22.623 +            """
  22.624 +        )
  22.625 +    }
  22.626 +    private val stmtUpdateIssue by lazy {
  22.627 +        connection.prepareStatement(
  22.628 +            """
  22.629 +            update lpit_issue set updated = now(),
  22.630 +                component = ?, status = ?::issue_status, category = ?::issue_category, subject = ?,
  22.631 +                description = ?, assignee = ?, eta = ?
  22.632 +            where issueid = ?
  22.633 +            """
  22.634 +        )
  22.635 +    }
  22.636 +    private val stmtInsertAffectedVersion by lazy {
  22.637 +        connection.prepareStatement(
  22.638 +            "insert into lpit_issue_affected_version (issueid, versionid) values (?,?)"
  22.639 +        )
  22.640 +    }
  22.641 +    private val stmtInsertResolvedVersion by lazy {
  22.642 +        connection.prepareStatement(
  22.643 +            "insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)"
  22.644 +        )
  22.645 +    }
  22.646 +    private val stmtClearAffectedVersions by lazy {
  22.647 +        connection.prepareStatement("delete from lpit_issue_affected_version where issueid = ?")
  22.648 +    }
  22.649 +    private val stmtClearResolvedVersions by lazy {
  22.650 +        connection.prepareStatement("delete from lpit_issue_resolved_version where issueid = ?")
  22.651 +    }
  22.652 +
  22.653 +    override fun findIssue(id: Int): Issue? {
  22.654 +        stmtFindIssueByID.setInt(1, id)
  22.655 +        return selectIssues(stmtFindIssueByID).firstOrNull()
  22.656 +    }
  22.657 +
  22.658 +    private fun insertVersionInfo(issue: Issue) {
  22.659 +        stmtInsertAffectedVersion.setInt(1, issue.id)
  22.660 +        stmtInsertResolvedVersion.setInt(1, issue.id)
  22.661 +        issue.affectedVersions.forEach {
  22.662 +            stmtInsertAffectedVersion.setInt(2, it.id)
  22.663 +            stmtInsertAffectedVersion.execute()
  22.664 +        }
  22.665 +        issue.resolvedVersions.forEach {
  22.666 +            stmtInsertResolvedVersion.setInt(2, it.id)
  22.667 +            stmtInsertResolvedVersion.execute()
  22.668 +        }
  22.669 +    }
  22.670 +
  22.671 +    override fun insertIssue(issue: Issue) {
  22.672 +        val col = setIssueFields(stmtInsertIssue, issue)
  22.673 +        stmtInsertIssue.setInt(col, issue.project.id)
  22.674 +        stmtInsertIssue.executeQuery().use { rs ->
  22.675 +            rs.next()
  22.676 +            issue.id = rs.getInt(1)
  22.677 +        }
  22.678 +        insertVersionInfo(issue)
  22.679 +    }
  22.680 +
  22.681 +    override fun updateIssue(issue: Issue) {
  22.682 +        val col = setIssueFields(stmtUpdateIssue, issue)
  22.683 +        stmtUpdateIssue.setInt(col, issue.id)
  22.684 +        // TODO: improve by only inserting / deleting changed version information
  22.685 +        stmtClearAffectedVersions.setInt(1, issue.id)
  22.686 +        stmtClearResolvedVersions.setInt(1, issue.id)
  22.687 +        stmtClearAffectedVersions.execute()
  22.688 +        stmtClearResolvedVersions.execute()
  22.689 +        insertVersionInfo(issue)
  22.690 +    }
  22.691 +
  22.692 +    //</editor-fold>
  22.693 +
  22.694 +    //<editor-fold desc="IssueComment">
  22.695 +
  22.696 +    private fun selectComments(stmt: PreparedStatement) = sequence {
  22.697 +        stmt.executeQuery().use { rs ->
  22.698 +            while (rs.next()) {
  22.699 +                yield(IssueComment(rs.getInt("commentid"), rs.getInt("issueid")).apply {
  22.700 +                    created = rs.getTimestamp("created")
  22.701 +                    updated = rs.getTimestamp("updated")
  22.702 +                    updateCount = rs.getInt("updatecount")
  22.703 +                    comment = rs.getString("comment")
  22.704 +                    author = selectUserInfo(rs)
  22.705 +                })
  22.706 +            }
  22.707 +        }
  22.708 +    }
  22.709 +
  22.710 +    private val stmtComments by lazy {
  22.711 +        connection.prepareStatement(
  22.712 +            "select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created"
  22.713 +        )
  22.714 +    }
  22.715 +    private val stmtInsertComment by lazy {
  22.716 +        connection.prepareStatement(
  22.717 +            "insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)"
  22.718 +        )
  22.719 +    }
  22.720 +    private val stmtUpdateIssueDate by lazy {
  22.721 +        connection.prepareStatement(
  22.722 +            "update lpit_issue set updated = now() where issueid = ?"
  22.723 +        )
  22.724 +    }
  22.725 +
  22.726 +    override fun listComments(issue: Issue): List<IssueComment> {
  22.727 +        stmtComments.setInt(1, issue.id)
  22.728 +        return selectComments(stmtComments).toList()
  22.729 +    }
  22.730 +
  22.731 +    override fun insertComment(issueComment: IssueComment) {
  22.732 +        with(issueComment) {
  22.733 +            stmtUpdateIssueDate.setInt(1, issueid)
  22.734 +            stmtInsertComment.setInt(1, issueid)
  22.735 +            stmtInsertComment.setStringSafe(2, comment)
  22.736 +            stmtInsertComment.setIntOrNull(3, author?.id)
  22.737 +        }
  22.738 +        stmtInsertComment.execute()
  22.739 +        stmtUpdateIssueDate.execute()
  22.740 +    }
  22.741 +    //</editor-fold>
  22.742 +}
  22.743 \ No newline at end of file
    23.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGComponentDao.kt	Sun Dec 20 11:06:25 2020 +0100
    23.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.3 @@ -1,110 +0,0 @@
    23.4 -/*
    23.5 - * Copyright 2020 Mike Becker. All rights reserved.
    23.6 - *
    23.7 - * Redistribution and use in source and binary forms, with or without
    23.8 - * modification, are permitted provided that the following conditions are met:
    23.9 - *
   23.10 - * 1. Redistributions of source code must retain the above copyright
   23.11 - * notice, this list of conditions and the following disclaimer.
   23.12 - *
   23.13 - * 2. Redistributions in binary form must reproduce the above copyright
   23.14 - * notice, this list of conditions and the following disclaimer in the
   23.15 - * documentation and/or other materials provided with the distribution.
   23.16 - *
   23.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   23.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   23.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   23.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   23.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   23.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   23.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   23.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   23.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   23.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   23.27 - *
   23.28 - */
   23.29 -
   23.30 -package de.uapcore.lightpit.dao.postgres
   23.31 -
   23.32 -import de.uapcore.lightpit.dao.AbstractComponentDao
   23.33 -import de.uapcore.lightpit.dao.Functions
   23.34 -import de.uapcore.lightpit.entities.Component
   23.35 -import de.uapcore.lightpit.entities.Project
   23.36 -import de.uapcore.lightpit.entities.User
   23.37 -import de.uapcore.lightpit.types.WebColor
   23.38 -import java.sql.Connection
   23.39 -import java.sql.PreparedStatement
   23.40 -import java.sql.ResultSet
   23.41 -
   23.42 -class PGComponentDao(connection: Connection) : AbstractComponentDao() {
   23.43 -
   23.44 -    private val query = "select id, name, node, color, ordinal, description, " +
   23.45 -            "userid, username, givenname, lastname, mail " +
   23.46 -            "from lpit_component " +
   23.47 -            "left join lpit_user on lead = userid"
   23.48 -
   23.49 -    private val listStmt = connection.prepareStatement("$query where project = ? order by ordinal, lower(name)")
   23.50 -    private val findStmt = connection.prepareStatement("$query where id = ? ")
   23.51 -    private val findByNodeStmt = connection.prepareStatement("$query where project = ? and node = ?")
   23.52 -    private val insertStmt = connection.prepareStatement(
   23.53 -            "insert into lpit_component (name, node, color, ordinal, description, lead, project) values (?, ?, ?, ?, ?, ?, ?)"
   23.54 -    )
   23.55 -    private val updateStmt = connection.prepareStatement(
   23.56 -            "update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?"
   23.57 -    )
   23.58 -
   23.59 -    override fun mapResult(rs: ResultSet): Component {
   23.60 -        val component = Component(rs.getInt("id"))
   23.61 -        component.name = rs.getString("name")
   23.62 -        component.node = rs.getString("node")
   23.63 -        component.color = try {
   23.64 -            WebColor(rs.getString("color"))
   23.65 -        } catch (ex: IllegalArgumentException) {
   23.66 -            WebColor("000000")
   23.67 -        }
   23.68 -        component.ordinal = rs.getInt("ordinal")
   23.69 -        component.description = rs.getString("description")
   23.70 -        component.lead = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() }
   23.71 -        return component
   23.72 -    }
   23.73 -
   23.74 -    private fun setColumns(stmt: PreparedStatement, instance: Component): Int {
   23.75 -        var column = 0
   23.76 -        stmt.setString(++column, instance.name)
   23.77 -        stmt.setString(++column, instance.node)
   23.78 -        stmt.setString(++column, instance.color.hex)
   23.79 -        stmt.setInt(++column, instance.ordinal)
   23.80 -        Functions.setStringOrNull(stmt, ++column, instance.description)
   23.81 -        setForeignKeyOrNull(stmt, ++column, instance.lead, User::id)
   23.82 -        return column
   23.83 -    }
   23.84 -
   23.85 -    override fun save(instance: Component, parent: Project) {
   23.86 -        var column = setColumns(insertStmt, instance)
   23.87 -        insertStmt.setInt(++column, parent.id)
   23.88 -        insertStmt.executeUpdate()
   23.89 -    }
   23.90 -
   23.91 -    override fun update(instance: Component): Boolean {
   23.92 -        var column = setColumns(updateStmt, instance)
   23.93 -        updateStmt.setInt(++column, instance.id)
   23.94 -        return updateStmt.executeUpdate() > 0
   23.95 -    }
   23.96 -
   23.97 -
   23.98 -    override fun list(parent: Project): List<Component> {
   23.99 -        listStmt.setInt(1, parent.id)
  23.100 -        return super.list(listStmt)
  23.101 -    }
  23.102 -
  23.103 -    override fun find(id: Int): Component? {
  23.104 -        findStmt.setInt(1, id)
  23.105 -        return super.find(findStmt)
  23.106 -    }
  23.107 -
  23.108 -    override fun findByNode(parent: Project, node: String): Component? {
  23.109 -        findByNodeStmt.setInt(1, parent.id)
  23.110 -        findByNodeStmt.setString(2, node)
  23.111 -        return super.find(findByNodeStmt)
  23.112 -    }
  23.113 -}
  23.114 \ No newline at end of file
    24.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGDaoProvider.kt	Sun Dec 20 11:06:25 2020 +0100
    24.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.3 @@ -1,38 +0,0 @@
    24.4 -/*
    24.5 - * Copyright 2020 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 -
   24.30 -package de.uapcore.lightpit.dao.postgres
   24.31 -
   24.32 -import de.uapcore.lightpit.dao.DaoProvider
   24.33 -import java.sql.Connection
   24.34 -
   24.35 -class PGDaoProvider(connection: Connection) : DaoProvider {
   24.36 -    override val userDao = PGUserDao(connection)
   24.37 -    override val projectDao = PGProjectDao(connection)
   24.38 -    override val componentDao = PGComponentDao(connection)
   24.39 -    override val versionDao = PGVersionDao(connection)
   24.40 -    override val issueDao = PGIssueDao(connection)
   24.41 -}
   24.42 \ No newline at end of file
    25.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGIssueDao.kt	Sun Dec 20 11:06:25 2020 +0100
    25.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.3 @@ -1,255 +0,0 @@
    25.4 -/*
    25.5 - * Copyright 2020 Mike Becker. All rights reserved.
    25.6 - *
    25.7 - * Redistribution and use in source and binary forms, with or without
    25.8 - * modification, are permitted provided that the following conditions are met:
    25.9 - *
   25.10 - * 1. Redistributions of source code must retain the above copyright
   25.11 - * notice, this list of conditions and the following disclaimer.
   25.12 - *
   25.13 - * 2. Redistributions in binary form must reproduce the above copyright
   25.14 - * notice, this list of conditions and the following disclaimer in the
   25.15 - * documentation and/or other materials provided with the distribution.
   25.16 - *
   25.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   25.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   25.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   25.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   25.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   25.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   25.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   25.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   25.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   25.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   25.27 - *
   25.28 - */
   25.29 -
   25.30 -package de.uapcore.lightpit.dao.postgres
   25.31 -
   25.32 -import de.uapcore.lightpit.dao.AbstractIssueDao
   25.33 -import de.uapcore.lightpit.dao.Functions
   25.34 -import de.uapcore.lightpit.entities.*
   25.35 -import java.sql.Connection
   25.36 -import java.sql.PreparedStatement
   25.37 -import java.sql.ResultSet
   25.38 -import java.sql.Types
   25.39 -
   25.40 -class PGIssueDao(connection: Connection) : AbstractIssueDao() {
   25.41 -
   25.42 -    private val query = "select issueid, i.project, p.name as projectname, p.node as projectnode, " +
   25.43 -            "component, c.name as componentname, c.node as componentnode, " +
   25.44 -            "status, category, subject, i.description, " +
   25.45 -            "userid, username, givenname, lastname, mail, " +
   25.46 -            "created, updated, eta " +
   25.47 -            "from lpit_issue i " +
   25.48 -            "join lpit_project p on i.project = projectid " +
   25.49 -            "left join lpit_component c on component = c.id " +
   25.50 -            "left join lpit_user on userid = assignee "
   25.51 -    private val list = connection.prepareStatement(query +
   25.52 -            "where i.project = ? and coalesce(component, -1) = coalesce(?, component, -1)")
   25.53 -    private val listForVersion = connection.prepareStatement(
   25.54 -            "with issue_version as ( " +
   25.55 -                    "select issueid, versionid from lpit_issue_affected_version union " +
   25.56 -                    "select issueid, versionid from lpit_issue_resolved_version) " +
   25.57 -                    query +
   25.58 -                    "left join issue_version using (issueid) " +
   25.59 -                    "where i.project = ? " +
   25.60 -                    "and coalesce(versionid,-1) = ? and coalesce(component, -1) = coalesce(?, component, -1)"
   25.61 -    )
   25.62 -    private val find = connection.prepareStatement(query + "where issueid = ? ")
   25.63 -    private val insert = connection.prepareStatement(
   25.64 -            "insert into lpit_issue (project, component, status, category, subject, description, assignee, eta) " +
   25.65 -                    "values (?, ?, ?::issue_status, ?::issue_category, ?, ?, ?, ?) returning issueid"
   25.66 -    )
   25.67 -    private val update = connection.prepareStatement(
   25.68 -            "update lpit_issue set " +
   25.69 -                    "updated = now(), component = ?, status = ?::issue_status, category = ?::issue_category, " +
   25.70 -                    "subject = ?, description = ?, assignee = ?, eta = ? where issueid = ?"
   25.71 -    )
   25.72 -    private val affectedVersions = connection.prepareStatement(
   25.73 -            "select versionid, name, status, ordinal, node " +
   25.74 -                    "from lpit_version join lpit_issue_affected_version using (versionid) " +
   25.75 -                    "where issueid = ? " +
   25.76 -                    "order by ordinal, name"
   25.77 -    )
   25.78 -    private val clearAffected = connection.prepareStatement("delete from lpit_issue_affected_version where issueid = ?")
   25.79 -    private val insertAffected = connection.prepareStatement("insert into lpit_issue_affected_version (issueid, versionid) values (?,?)")
   25.80 -
   25.81 -    private val resolvedVersions = connection.prepareStatement(
   25.82 -            "select versionid, name, status, ordinal, node " +
   25.83 -                    "from lpit_version v join lpit_issue_resolved_version using (versionid) " +
   25.84 -                    "where issueid = ? " +
   25.85 -                    "order by ordinal, name"
   25.86 -    )
   25.87 -    private val clearResolved = connection.prepareStatement("delete from lpit_issue_resolved_version where issueid = ?")
   25.88 -    private val insertResolved = connection.prepareStatement("insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)")
   25.89 -    private val insertComment = connection.prepareStatement(
   25.90 -            "insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)"
   25.91 -    )
   25.92 -    private val updateComment = connection.prepareStatement(
   25.93 -            "update lpit_issue_comment set comment = ?, updated = now(), updatecount = updatecount+1 where commentid = ?"
   25.94 -    )
   25.95 -    private val listComments = connection.prepareStatement(
   25.96 -            "select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created"
   25.97 -    )
   25.98 -
   25.99 -    private val updateIssueLastModified = connection.prepareStatement(
  25.100 -        "update lpit_issue set updated = now() where issueid = ?"
  25.101 -    );
  25.102 -
  25.103 -    override fun mapResult(rs: ResultSet): Issue {
  25.104 -        val project = Project(rs.getInt("project"))
  25.105 -        project.name = rs.getString("projectname")
  25.106 -        project.node = rs.getString("projectnode")
  25.107 -        val issue = Issue(rs.getInt("issueid"))
  25.108 -        issue.project = project
  25.109 -        issue.component = rs.getInt("component").let { id ->
  25.110 -            if (rs.wasNull()) {
  25.111 -                null
  25.112 -            } else {
  25.113 -                val component = Component(id)
  25.114 -                component.name = rs.getString("componentname")
  25.115 -                component.node = rs.getString("componentnode")
  25.116 -                component
  25.117 -            }
  25.118 -        }
  25.119 -        issue.status = IssueStatus.valueOf(rs.getString("status"))
  25.120 -        issue.category = IssueCategory.valueOf(rs.getString("category"))
  25.121 -        issue.subject = rs.getString("subject")
  25.122 -        issue.description = rs.getString("description")
  25.123 -        issue.assignee = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() }
  25.124 -        issue.created = rs.getTimestamp("created")
  25.125 -        issue.updated = rs.getTimestamp("updated")
  25.126 -        issue.eta = rs.getDate("eta")
  25.127 -        return issue
  25.128 -    }
  25.129 -
  25.130 -    private fun updateVersionLists(instance: Issue) {
  25.131 -        clearAffected.setInt(1, instance.id)
  25.132 -        clearResolved.setInt(1, instance.id)
  25.133 -        insertAffected.setInt(1, instance.id)
  25.134 -        insertResolved.setInt(1, instance.id)
  25.135 -        clearAffected.executeUpdate()
  25.136 -        clearResolved.executeUpdate()
  25.137 -        for (v: Version in instance.affectedVersions) {
  25.138 -            insertAffected.setInt(2, v.id)
  25.139 -            insertAffected.executeUpdate()
  25.140 -        }
  25.141 -        for (v: Version in instance.resolvedVersions) {
  25.142 -            insertResolved.setInt(2, v.id)
  25.143 -            insertResolved.executeUpdate()
  25.144 -        }
  25.145 -    }
  25.146 -
  25.147 -    private fun setData(stmt: PreparedStatement, column: Int, instance: Issue): Int {
  25.148 -        var col = column
  25.149 -        setForeignKeyOrNull(stmt, ++col, instance.component, Component::id)
  25.150 -        stmt.setString(++col, instance.status.name)
  25.151 -        stmt.setString(++col, instance.category.name)
  25.152 -        stmt.setString(++col, instance.subject)
  25.153 -        Functions.setStringOrNull(stmt, ++col, instance.description)
  25.154 -        setForeignKeyOrNull(stmt, ++col, instance.assignee, User::id)
  25.155 -        Functions.setDateOrNull(stmt, ++col, instance.eta)
  25.156 -        return col
  25.157 -    }
  25.158 -
  25.159 -    override fun save(instance: Issue, parent: Project) {
  25.160 -        instance.project = parent
  25.161 -        var column = 0
  25.162 -        insert.setInt(++column, parent.id)
  25.163 -        setData(insert, column, instance)
  25.164 -        // insert and retrieve the ID
  25.165 -        val rs = insert.executeQuery()
  25.166 -        rs.next()
  25.167 -        instance.id = rs.getInt(1)
  25.168 -        updateVersionLists(instance)
  25.169 -    }
  25.170 -
  25.171 -    override fun update(instance: Issue): Boolean {
  25.172 -        var column = setData(update, 0, instance)
  25.173 -        update.setInt(++column, instance.id)
  25.174 -        return if (update.executeUpdate() > 0) {
  25.175 -            updateVersionLists(instance)
  25.176 -            true
  25.177 -        } else {
  25.178 -            false
  25.179 -        }
  25.180 -    }
  25.181 -
  25.182 -    override fun list(parent: Project): List<Issue> {
  25.183 -        list.setInt(1, parent.id)
  25.184 -        list.setNull(2, Types.INTEGER)
  25.185 -        return super.list(list)
  25.186 -    }
  25.187 -
  25.188 -    override fun list(project: Project, component: Component?, version: Version?): List<Issue> {
  25.189 -        listForVersion.setInt(1, project.id)
  25.190 -        listForVersion.setInt(2, version?.id ?: -1)
  25.191 -        listForVersion.setInt(3, component?.id ?: -1)
  25.192 -        return super.list(listForVersion)
  25.193 -    }
  25.194 -
  25.195 -    override fun list(project: Project, version: Version?): List<Issue> {
  25.196 -        listForVersion.setInt(1, project.id)
  25.197 -        listForVersion.setInt(2, version?.id ?: -1)
  25.198 -        listForVersion.setNull(3, Types.INTEGER)
  25.199 -        return super.list(listForVersion)
  25.200 -    }
  25.201 -
  25.202 -    override fun list(project: Project, component: Component?): List<Issue> {
  25.203 -        list.setInt(1, project.id)
  25.204 -        list.setInt(2, component?.id ?: -1)
  25.205 -        return super.list(list)
  25.206 -    }
  25.207 -
  25.208 -    override fun find(id: Int): Issue? {
  25.209 -        find.setInt(1, id)
  25.210 -        return super.find(find)
  25.211 -    }
  25.212 -
  25.213 -    private fun listVersions(stmt: PreparedStatement, issue: Issue): List<Version> {
  25.214 -        stmt.setInt(1, issue.id)
  25.215 -        return sequence {
  25.216 -            stmt.executeQuery().use { result ->
  25.217 -                while (result.next()) yield(PGVersionDao.mapResult(result))
  25.218 -            }
  25.219 -        }.toList()
  25.220 -    }
  25.221 -
  25.222 -    override fun joinVersionInformation(issue: Issue) {
  25.223 -        issue.affectedVersions = listVersions(affectedVersions, issue)
  25.224 -        issue.resolvedVersions = listVersions(resolvedVersions, issue)
  25.225 -    }
  25.226 -
  25.227 -    override fun listComments(issue: Issue): List<IssueComment> {
  25.228 -        listComments.setInt(1, issue.id)
  25.229 -        return sequence {
  25.230 -            listComments.executeQuery().use { rs ->
  25.231 -                while (rs.next()) {
  25.232 -                    val comment = IssueComment(rs.getInt("commentid"))
  25.233 -                    comment.created = rs.getTimestamp("created")
  25.234 -                    comment.updated = rs.getTimestamp("updated")
  25.235 -                    comment.updateCount = rs.getInt("updatecount")
  25.236 -                    comment.comment = rs.getString("comment")
  25.237 -                    comment.author = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() }
  25.238 -                    yield(comment)
  25.239 -                }
  25.240 -            }
  25.241 -        }.toList()
  25.242 -    }
  25.243 -
  25.244 -    override fun saveComment(issue: Issue, comment: IssueComment) {
  25.245 -        if (comment.id >= 0) {
  25.246 -            updateComment.setString(1, comment.comment)
  25.247 -            updateComment.setInt(2, comment.id)
  25.248 -            updateComment.execute()
  25.249 -        } else {
  25.250 -            insertComment.setInt(1, issue.id)
  25.251 -            insertComment.setString(2, comment.comment)
  25.252 -            setForeignKeyOrNull(insertComment, 3, comment.author, User::id)
  25.253 -            insertComment.execute()
  25.254 -        }
  25.255 -        updateIssueLastModified.setInt(1, issue.id);
  25.256 -        updateIssueLastModified.execute();
  25.257 -    }
  25.258 -}
  25.259 \ No newline at end of file
    26.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGProjectDao.kt	Sun Dec 20 11:06:25 2020 +0100
    26.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.3 @@ -1,122 +0,0 @@
    26.4 -/*
    26.5 - * Copyright 2020 Mike Becker. All rights reserved.
    26.6 - *
    26.7 - * Redistribution and use in source and binary forms, with or without
    26.8 - * modification, are permitted provided that the following conditions are met:
    26.9 - *
   26.10 - * 1. Redistributions of source code must retain the above copyright
   26.11 - * notice, this list of conditions and the following disclaimer.
   26.12 - *
   26.13 - * 2. Redistributions in binary form must reproduce the above copyright
   26.14 - * notice, this list of conditions and the following disclaimer in the
   26.15 - * documentation and/or other materials provided with the distribution.
   26.16 - *
   26.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   26.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   26.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   26.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   26.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   26.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   26.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   26.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   26.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   26.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   26.27 - *
   26.28 - */
   26.29 -
   26.30 -package de.uapcore.lightpit.dao.postgres
   26.31 -
   26.32 -import de.uapcore.lightpit.dao.AbstractProjectDao
   26.33 -import de.uapcore.lightpit.dao.Functions
   26.34 -import de.uapcore.lightpit.entities.IssueSummary
   26.35 -import de.uapcore.lightpit.entities.Project
   26.36 -import de.uapcore.lightpit.entities.User
   26.37 -import java.sql.Connection
   26.38 -import java.sql.PreparedStatement
   26.39 -import java.sql.ResultSet
   26.40 -
   26.41 -class PGProjectDao(connection: Connection) : AbstractProjectDao() {
   26.42 -
   26.43 -    private val query = "select projectid, name, node, description, repourl, " +
   26.44 -            "userid, username, lastname, givenname, mail " +
   26.45 -            "from lpit_project " +
   26.46 -            "left join lpit_user owner on lpit_project.owner = owner.userid "
   26.47 -
   26.48 -    private val listStmt = connection.prepareStatement("$query order by name")
   26.49 -    private val findStmt = connection.prepareStatement("$query where projectid = ?")
   26.50 -    private val findByNodeStmt = connection.prepareStatement("$query where node = ?")
   26.51 -    private val issueSummaryStmt = connection.prepareStatement(
   26.52 -            "select phase, count(*) as total " +
   26.53 -                    "from lpit_issue " +
   26.54 -                    "join lpit_issue_phases using(status) " +
   26.55 -                    "where project = ? " +
   26.56 -                    "group by phase "
   26.57 -    )
   26.58 -    private val insertStmt = connection.prepareStatement(
   26.59 -            "insert into lpit_project (name, node, description, repourl, owner) values (?, ?, ?, ?, ?)"
   26.60 -    )
   26.61 -    private val updateStmt = connection.prepareStatement(
   26.62 -            "update lpit_project set name = ?, node = ?, description = ?, repourl = ?, owner = ? where projectid = ?"
   26.63 -    )
   26.64 -
   26.65 -    override fun mapResult(rs: ResultSet): Project {
   26.66 -        val proj = Project(rs.getInt("projectid"))
   26.67 -        proj.name = rs.getString("name")
   26.68 -        proj.node = rs.getString("node")
   26.69 -        proj.description = rs.getString("description")
   26.70 -        proj.repoUrl = rs.getString("repourl")
   26.71 -        proj.owner = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() }
   26.72 -        return proj
   26.73 -    }
   26.74 -
   26.75 -    override fun getIssueSummary(project: Project): IssueSummary {
   26.76 -        issueSummaryStmt.setInt(1, project.id)
   26.77 -        val result = issueSummaryStmt.executeQuery()
   26.78 -        val summary = IssueSummary()
   26.79 -        while (result.next()) {
   26.80 -            val phase = result.getInt("phase")
   26.81 -            val total = result.getInt("total")
   26.82 -            when (phase) {
   26.83 -                0 -> summary.open = total
   26.84 -                1 -> summary.active = total
   26.85 -                2 -> summary.done = total
   26.86 -            }
   26.87 -        }
   26.88 -        return summary
   26.89 -    }
   26.90 -
   26.91 -    private fun setColumns(stmt: PreparedStatement, instance: Project): Int {
   26.92 -        var column = 0
   26.93 -        stmt.setString(++column, instance.name)
   26.94 -        stmt.setString(++column, instance.node)
   26.95 -        Functions.setStringOrNull(stmt, ++column, instance.description)
   26.96 -        Functions.setStringOrNull(stmt, ++column, instance.repoUrl)
   26.97 -        setForeignKeyOrNull(stmt, ++column, instance.owner, User::id)
   26.98 -        return column
   26.99 -    }
  26.100 -
  26.101 -    override fun save(instance: Project) {
  26.102 -        setColumns(insertStmt, instance)
  26.103 -        insertStmt.executeUpdate()
  26.104 -    }
  26.105 -
  26.106 -    override fun update(instance: Project): Boolean {
  26.107 -        var column = setColumns(updateStmt, instance)
  26.108 -        updateStmt.setInt(++column, instance.id)
  26.109 -        return updateStmt.executeUpdate() > 0
  26.110 -    }
  26.111 -
  26.112 -    override fun list(): List<Project> {
  26.113 -        return super.list(listStmt)
  26.114 -    }
  26.115 -
  26.116 -    override fun find(id: Int): Project? {
  26.117 -        findStmt.setInt(1, id)
  26.118 -        return super.find(findStmt)
  26.119 -    }
  26.120 -
  26.121 -    override fun findByNode(node: String): Project? {
  26.122 -        findByNodeStmt.setString(1, node)
  26.123 -        return super.find(findByNodeStmt)
  26.124 -    }
  26.125 -}
  26.126 \ No newline at end of file
    27.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGUserDao.kt	Sun Dec 20 11:06:25 2020 +0100
    27.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.3 @@ -1,95 +0,0 @@
    27.4 -/*
    27.5 - * Copyright 2020 Mike Becker. All rights reserved.
    27.6 - *
    27.7 - * Redistribution and use in source and binary forms, with or without
    27.8 - * modification, are permitted provided that the following conditions are met:
    27.9 - *
   27.10 - * 1. Redistributions of source code must retain the above copyright
   27.11 - * notice, this list of conditions and the following disclaimer.
   27.12 - *
   27.13 - * 2. Redistributions in binary form must reproduce the above copyright
   27.14 - * notice, this list of conditions and the following disclaimer in the
   27.15 - * documentation and/or other materials provided with the distribution.
   27.16 - *
   27.17 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   27.18 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   27.19 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   27.20 - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   27.21 - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   27.22 - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   27.23 - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   27.24 - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   27.25 - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   27.26 - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   27.27 - *
   27.28 - */
   27.29 -
   27.30 -package de.uapcore.lightpit.dao.postgres
   27.31 -
   27.32 -import de.uapcore.lightpit.dao.AbstractUserDao
   27.33 -import de.uapcore.lightpit.dao.Functions
   27.34 -import de.uapcore.lightpit.entities.User
   27.35 -import java.sql.Connection
   27.36 -import java.sql.ResultSet
   27.37 -
   27.38 -class PGUserDao(connection: Connection) : AbstractUserDao() {
   27.39 -
   27.40 -    companion object {
   27.41 -        fun mapResult(rs: ResultSet): User {
   27.42 -            val id = rs.getInt("userid")
   27.43 -            return if (rs.wasNull()) {
   27.44 -                User(-1)
   27.45 -            } else {
   27.46 -                val user = User(id)
   27.47 -                user.username = rs.getString("username")
   27.48 -                user.givenname = Functions.getSafeString(rs, "givenname")
   27.49 -                user.lastname = Functions.getSafeString(rs, "lastname")
   27.50 -                user.mail = Functions.getSafeString(rs, "mail")
   27.51 -                user
   27.52 -            }
   27.53 -        }
   27.54 -    }
   27.55 -
   27.56 -    private val listStmt = connection.prepareStatement(
   27.57 -            "select userid, username, lastname, givenname, mail " +
   27.58 -                    "from lpit_user where userid >= 0 " +
   27.59 -                    "order by username")
   27.60 -    private val findStmt = connection.prepareStatement(
   27.61 -            "select userid, username, lastname, givenname, mail " +
   27.62 -                    "from lpit_user where userid = ? ")
   27.63 -    private val findByUsernameStmt = connection.prepareStatement(
   27.64 -            "select userid, username, lastname, givenname, mail " +
   27.65 -                    "from lpit_user where lower(username) = lower(?) ")
   27.66 -    private val insertStmt = connection.prepareStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)")
   27.67 -    private val updateStmt = connection.prepareStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?")
   27.68 -
   27.69 -    override fun mapResult(rs: ResultSet): User = Companion.mapResult(rs)
   27.70 -
   27.71 -    override fun save(instance: User) {
   27.72 -        insertStmt.setString(1, instance.username)
   27.73 -        Functions.setStringOrNull(insertStmt, 2, instance.lastname)
   27.74 -        Functions.setStringOrNull(insertStmt, 3, instance.givenname)
   27.75 -        Functions.setStringOrNull(insertStmt, 4, instance.mail)
   27.76 -        insertStmt.executeUpdate()
   27.77 -    }
   27.78 -
   27.79 -    override fun update(instance: User): Boolean {
   27.80 -        Functions.setStringOrNull(updateStmt, 1, instance.lastname)
   27.81 -        Functions.setStringOrNull(updateStmt, 2, instance.givenname)
   27.82 -        Functions.setStringOrNull(updateStmt, 3, instance.mail)
   27.83 -        updateStmt.setInt(4, instance.id)
   27.84 -        return updateStmt.executeUpdate() > 0
   27.85 -    }
   27.86 -
   27.87 -    override fun list(): List<User> = super.list(listStmt)
   27.88 -
   27.89 -    override fun find(id: Int): User? {
   27.90 -        findStmt.setInt(1, id)
   27.91 -        return super.find(findStmt)
   27.92 -    }
   27.93 -
   27.94 -    override fun findByUsername(username: String): User? {
   27.95 -        findByUsernameStmt.setString(1, username)
   27.96 -        return super.find(findByUsernameStmt)
   27.97 -    }
   27.98 -}
   27.99 \ No newline at end of file
    28.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGVersionDao.kt	Sun Dec 20 11:06:25 2020 +0100
    28.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.3 @@ -1,105 +0,0 @@
    28.4 -/*
    28.5 - * Copyright 2020 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 -
   28.30 -package de.uapcore.lightpit.dao.postgres
   28.31 -
   28.32 -import de.uapcore.lightpit.dao.AbstractVersionDao
   28.33 -import de.uapcore.lightpit.entities.Project
   28.34 -import de.uapcore.lightpit.entities.Version
   28.35 -import de.uapcore.lightpit.entities.VersionStatus
   28.36 -import java.sql.Connection
   28.37 -import java.sql.PreparedStatement
   28.38 -import java.sql.ResultSet
   28.39 -
   28.40 -class PGVersionDao(connection: Connection) : AbstractVersionDao() {
   28.41 -
   28.42 -    companion object {
   28.43 -        fun mapResult(rs: ResultSet): Version {
   28.44 -            val id = rs.getInt("versionid")
   28.45 -            return if (rs.wasNull()) {
   28.46 -                Version(-1)
   28.47 -            } else {
   28.48 -                val version = Version(id)
   28.49 -                version.name = rs.getString("name")
   28.50 -                version.node = rs.getString("node")
   28.51 -                version.ordinal = rs.getInt("ordinal")
   28.52 -                version.status = VersionStatus.valueOf(rs.getString("status"))
   28.53 -                version
   28.54 -            }
   28.55 -        }
   28.56 -    }
   28.57 -
   28.58 -    private val query = "select versionid, project, name, node, ordinal, status from lpit_version"
   28.59 -    private val listStmt = connection.prepareStatement(query + " where project = ? " +
   28.60 -            "order by ordinal desc, lower(name) desc")
   28.61 -    private val findStmt = connection.prepareStatement("$query where versionid = ?")
   28.62 -    private val findByNodeStmt = connection.prepareStatement("$query where project = ? and node = ?")
   28.63 -    private val insertStmt = connection.prepareStatement(
   28.64 -            "insert into lpit_version (name, node, ordinal, status, project) values (?, ?, ?, ?::version_status, ?)"
   28.65 -    )
   28.66 -    private val updateStmt = connection.prepareStatement(
   28.67 -            "update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status where versionid = ?"
   28.68 -    )
   28.69 -
   28.70 -    override fun mapResult(rs: ResultSet): Version = Companion.mapResult(rs)
   28.71 -
   28.72 -    private fun setFields(stmt: PreparedStatement, instance: Version): Int {
   28.73 -        var column = 0
   28.74 -        stmt.setString(++column, instance.name)
   28.75 -        stmt.setString(++column, instance.node)
   28.76 -        stmt.setInt(++column, instance.ordinal)
   28.77 -        stmt.setString(++column, instance.status.name)
   28.78 -        return column
   28.79 -    }
   28.80 -
   28.81 -    override fun save(instance: Version, parent: Project) {
   28.82 -        var column = setFields(insertStmt, instance)
   28.83 -        insertStmt.setInt(++column, parent.id)
   28.84 -        insertStmt.executeUpdate()
   28.85 -    }
   28.86 -
   28.87 -    override fun update(instance: Version): Boolean {
   28.88 -        var column = setFields(updateStmt, instance)
   28.89 -        updateStmt.setInt(++column, instance.id)
   28.90 -        return updateStmt.executeUpdate() > 0
   28.91 -    }
   28.92 -
   28.93 -    override fun list(parent: Project): List<Version> {
   28.94 -        listStmt.setInt(1, parent.id)
   28.95 -        return super.list(listStmt)
   28.96 -    }
   28.97 -
   28.98 -    override fun find(id: Int): Version? {
   28.99 -        findStmt.setInt(1, id)
  28.100 -        return super.find(findStmt)
  28.101 -    }
  28.102 -
  28.103 -    override fun findByNode(parent: Project, node: String): Version? {
  28.104 -        findByNodeStmt.setInt(1, parent.id)
  28.105 -        findByNodeStmt.setString(2, node)
  28.106 -        return super.find(findByNodeStmt)
  28.107 -    }
  28.108 -}
  28.109 \ No newline at end of file
    29.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt	Sun Dec 20 11:06:25 2020 +0100
    29.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt	Mon Dec 21 18:29:34 2020 +0100
    29.3 @@ -27,7 +27,7 @@
    29.4  
    29.5  import de.uapcore.lightpit.types.WebColor
    29.6  
    29.7 -data class Component(val id: Int) {
    29.8 +data class Component(override val id: Int, var projectid: Int) : Entity {
    29.9      var name: String = ""
   29.10      var node: String = name
   29.11      var color = WebColor("000000")
    30.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Entity.kt	Mon Dec 21 18:29:34 2020 +0100
    30.3 @@ -0,0 +1,30 @@
    30.4 +/*
    30.5 + * Copyright 2020 Mike Becker. All rights reserved.
    30.6 + *
    30.7 + * Redistribution and use in source and binary forms, with or without
    30.8 + * modification, are permitted provided that the following conditions are met:
    30.9 + *
   30.10 + * 1. Redistributions of source code must retain the above copyright
   30.11 + * notice, this list of conditions and the following disclaimer.
   30.12 + *
   30.13 + * 2. Redistributions in binary form must reproduce the above copyright
   30.14 + * notice, this list of conditions and the following disclaimer in the
   30.15 + * documentation and/or other materials provided with the distribution.
   30.16 + *
   30.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   30.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   30.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   30.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   30.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   30.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   30.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   30.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   30.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   30.27 + */
   30.28 +
   30.29 +package de.uapcore.lightpit.entities
   30.30 +
   30.31 +interface Entity {
   30.32 +    val id: Int
   30.33 +}
   30.34 \ No newline at end of file
    31.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Sun Dec 20 11:06:25 2020 +0100
    31.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Mon Dec 21 18:29:34 2020 +0100
    31.3 @@ -25,81 +25,32 @@
    31.4  
    31.5  package de.uapcore.lightpit.entities
    31.6  
    31.7 +import de.uapcore.lightpit.types.IssueCategory
    31.8 +import de.uapcore.lightpit.types.IssueStatus
    31.9 +import de.uapcore.lightpit.types.IssueStatusPhase
   31.10  import java.sql.Date
   31.11  import java.sql.Timestamp
   31.12  import java.time.Instant
   31.13 -import kotlin.math.roundToInt
   31.14  
   31.15 -data class IssueStatusPhase(val number: Int) {
   31.16 -    companion object {
   31.17 -        val Open = IssueStatusPhase(0)
   31.18 -        val WorkInProgress = IssueStatusPhase(1)
   31.19 -        val Done = IssueStatusPhase(2)
   31.20 -    }
   31.21 -}
   31.22 -
   31.23 -enum class IssueStatus(val phase: IssueStatusPhase) {
   31.24 -    InSpecification(IssueStatusPhase.Open),
   31.25 -    ToDo(IssueStatusPhase.Open),
   31.26 -    Scheduled(IssueStatusPhase.Open),
   31.27 -    InProgress(IssueStatusPhase.WorkInProgress),
   31.28 -    InReview(IssueStatusPhase.WorkInProgress),
   31.29 -    Done(IssueStatusPhase.Done),
   31.30 -    Rejected(IssueStatusPhase.Done),
   31.31 -    Withdrawn(IssueStatusPhase.Done),
   31.32 -    Duplicate(IssueStatusPhase.Done);
   31.33 -}
   31.34 -
   31.35 -enum class IssueCategory {
   31.36 -    Feature, Improvement, Bug, Task, Test
   31.37 -}
   31.38 -
   31.39 -data class Issue(var id: Int) {
   31.40 -
   31.41 -    var project: Project? = null
   31.42 -    var component: Component? = null
   31.43 +data class Issue(override var id: Int, var project: Project, var component: Component? = null) : Entity {
   31.44  
   31.45      var status = IssueStatus.InSpecification
   31.46      var category = IssueCategory.Feature
   31.47  
   31.48 -    var subject: String? = null
   31.49 +    var subject: String = ""
   31.50      var description: String? = null
   31.51      var assignee: User? = null
   31.52  
   31.53 -    var affectedVersions = emptyList<Version>()
   31.54 -    var resolvedVersions = emptyList<Version>()
   31.55 -
   31.56      var created: Timestamp = Timestamp.from(Instant.now())
   31.57      var updated: Timestamp = Timestamp.from(Instant.now())
   31.58      var eta: Date? = null
   31.59  
   31.60 +    var affectedVersions = emptyList<Version>()
   31.61 +    var resolvedVersions = emptyList<Version>()
   31.62 +
   31.63      /**
   31.64       * An issue is overdue, if it is not done and the ETA is before the current time.
   31.65       */
   31.66      val overdue get() = status.phase != IssueStatusPhase.Done && eta?.before(Date(System.currentTimeMillis())) ?: false
   31.67  }
   31.68  
   31.69 -class IssueSummary {
   31.70 -    var open = 0
   31.71 -    var active = 0
   31.72 -    var done = 0
   31.73 -
   31.74 -    val total get() = open + active + done
   31.75 -
   31.76 -    val openPercent get() = 100 - activePercent - donePercent
   31.77 -    val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0
   31.78 -    val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100
   31.79 -
   31.80 -    /**
   31.81 -     * Adds the specified issue to the summary by incrementing the respective counter.
   31.82 -     * @param issue the issue
   31.83 -     */
   31.84 -    fun add(issue: Issue) {
   31.85 -        when (issue.status.phase) {
   31.86 -            IssueStatusPhase.Open -> open++
   31.87 -            IssueStatusPhase.WorkInProgress -> active++
   31.88 -            IssueStatusPhase.Done -> done++
   31.89 -        }
   31.90 -    }
   31.91 -}
   31.92 -
    32.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt	Sun Dec 20 11:06:25 2020 +0100
    32.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt	Mon Dec 21 18:29:34 2020 +0100
    32.3 @@ -28,9 +28,9 @@
    32.4  import java.sql.Timestamp
    32.5  import java.time.Instant
    32.6  
    32.7 -data class IssueComment(val id: Int) {
    32.8 +data class IssueComment(override val id: Int, val issueid: Int) : Entity {
    32.9      var author: User? = null
   32.10 -    var comment: String? = null
   32.11 +    var comment: String = ""
   32.12      var created: Timestamp = Timestamp.from(Instant.now())
   32.13      var updated: Timestamp = Timestamp.from(Instant.now())
   32.14      var updateCount = 0
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueSummary.kt	Mon Dec 21 18:29:34 2020 +0100
    33.3 @@ -0,0 +1,53 @@
    33.4 +/*
    33.5 + * Copyright 2020 Mike Becker. All rights reserved.
    33.6 + *
    33.7 + * Redistribution and use in source and binary forms, with or without
    33.8 + * modification, are permitted provided that the following conditions are met:
    33.9 + *
   33.10 + * 1. Redistributions of source code must retain the above copyright
   33.11 + * notice, this list of conditions and the following disclaimer.
   33.12 + *
   33.13 + * 2. Redistributions in binary form must reproduce the above copyright
   33.14 + * notice, this list of conditions and the following disclaimer in the
   33.15 + * documentation and/or other materials provided with the distribution.
   33.16 + *
   33.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   33.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   33.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   33.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   33.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   33.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   33.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   33.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   33.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   33.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   33.27 + */
   33.28 +
   33.29 +package de.uapcore.lightpit.entities
   33.30 +
   33.31 +import de.uapcore.lightpit.types.IssueStatusPhase
   33.32 +import kotlin.math.roundToInt
   33.33 +
   33.34 +class IssueSummary {
   33.35 +    var open = 0
   33.36 +    var active = 0
   33.37 +    var done = 0
   33.38 +
   33.39 +    val total get() = open + active + done
   33.40 +
   33.41 +    val openPercent get() = 100 - activePercent - donePercent
   33.42 +    val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0
   33.43 +    val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100
   33.44 +
   33.45 +    /**
   33.46 +     * Adds the specified issue to the summary by incrementing the respective counter.
   33.47 +     * @param issue the issue
   33.48 +     */
   33.49 +    fun add(issue: Issue) {
   33.50 +        when (issue.status.phase) {
   33.51 +            IssueStatusPhase.Open -> open++
   33.52 +            IssueStatusPhase.WorkInProgress -> active++
   33.53 +            IssueStatusPhase.Done -> done++
   33.54 +        }
   33.55 +    }
   33.56 +}
   33.57 \ No newline at end of file
    34.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt	Sun Dec 20 11:06:25 2020 +0100
    34.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt	Mon Dec 21 18:29:34 2020 +0100
    34.3 @@ -25,9 +25,9 @@
    34.4  
    34.5  package de.uapcore.lightpit.entities
    34.6  
    34.7 -data class Project(val id: Int) {
    34.8 -    var name: String? = null
    34.9 -    var node: String? = null
   34.10 +data class Project(override val id: Int) : Entity {
   34.11 +    var name: String = ""
   34.12 +    var node: String = name
   34.13      var description: String? = null
   34.14      var repoUrl: String? = null
   34.15      var owner: User? = null
    35.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Sun Dec 20 11:06:25 2020 +0100
    35.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Mon Dec 21 18:29:34 2020 +0100
    35.3 @@ -25,16 +25,17 @@
    35.4  
    35.5  package de.uapcore.lightpit.entities
    35.6  
    35.7 -data class User(val id: Int) {
    35.8 -    var username = ""
    35.9 -    var mail = ""
   35.10 -    var givenname = ""
   35.11 -    var lastname = ""
   35.12 +data class User(override val id: Int) : Entity {
   35.13 +    var username: String = ""
   35.14 +    var mail: String? = null
   35.15 +    var givenname: String? = null
   35.16 +    var lastname: String? = null
   35.17  
   35.18 -    val shortDisplayname: String get() {
   35.19 -        val str = "$givenname $lastname"
   35.20 -        return if (str.isBlank()) username else str.trim()
   35.21 -    }
   35.22 +    val shortDisplayname: String
   35.23 +        get() {
   35.24 +            val str = "${givenname ?: ""} ${lastname ?: ""}"
   35.25 +            return if (str.isBlank()) username else str.trim()
   35.26 +        }
   35.27  
   35.28 -    val displayname: String get() = if (mail.isBlank()) shortDisplayname else "$shortDisplayname <$mail>"
   35.29 +    val displayname: String get() = if (mail.isNullOrBlank()) shortDisplayname else "$shortDisplayname <$mail>"
   35.30  }
   35.31 \ No newline at end of file
    36.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt	Sun Dec 20 11:06:25 2020 +0100
    36.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt	Mon Dec 21 18:29:34 2020 +0100
    36.3 @@ -25,12 +25,9 @@
    36.4  
    36.5  package de.uapcore.lightpit.entities
    36.6  
    36.7 -enum class VersionStatus {
    36.8 -    Future, Unreleased, Released, LTS, Deprecated;
    36.9 -    val isReleased get() = this.ordinal >= Released.ordinal
   36.10 -}
   36.11 +import de.uapcore.lightpit.types.VersionStatus
   36.12  
   36.13 -data class Version(val id: Int) : Comparable<Version> {
   36.14 +data class Version(override val id: Int, var projectid: Int) : Entity, Comparable<Version> {
   36.15      var name: String = ""
   36.16      var node = name
   36.17      var ordinal = 0
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/src/main/kotlin/de/uapcore/lightpit/filter/Filter.kt	Mon Dec 21 18:29:34 2020 +0100
    37.3 @@ -0,0 +1,32 @@
    37.4 +/*
    37.5 + * Copyright 2020 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 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.2 +++ b/src/main/kotlin/de/uapcore/lightpit/filter/IssueFilter.kt	Mon Dec 21 18:29:34 2020 +0100
    38.3 @@ -0,0 +1,36 @@
    38.4 +/*
    38.5 + * Copyright 2020 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/types/IssueCategory.kt	Mon Dec 21 18:29:34 2020 +0100
    39.3 @@ -0,0 +1,30 @@
    39.4 +/*
    39.5 + * Copyright 2020 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.types
   39.30 +
   39.31 +enum class IssueCategory {
   39.32 +    Feature, Improvement, Bug, Task, Test
   39.33 +}
   39.34 \ 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/types/IssueStatus.kt	Mon Dec 21 18:29:34 2020 +0100
    40.3 @@ -0,0 +1,38 @@
    40.4 +/*
    40.5 + * Copyright 2020 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.types
   40.30 +
   40.31 +enum class IssueStatus(val phase: IssueStatusPhase) {
   40.32 +    InSpecification(IssueStatusPhase.Open),
   40.33 +    ToDo(IssueStatusPhase.Open),
   40.34 +    Scheduled(IssueStatusPhase.Open),
   40.35 +    InProgress(IssueStatusPhase.WorkInProgress),
   40.36 +    InReview(IssueStatusPhase.WorkInProgress),
   40.37 +    Done(IssueStatusPhase.Done),
   40.38 +    Rejected(IssueStatusPhase.Done),
   40.39 +    Withdrawn(IssueStatusPhase.Done),
   40.40 +    Duplicate(IssueStatusPhase.Done);
   40.41 +}
   40.42 \ 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/types/IssueStatusPhase.kt	Mon Dec 21 18:29:34 2020 +0100
    41.3 @@ -0,0 +1,34 @@
    41.4 +/*
    41.5 + * Copyright 2020 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.types
   41.30 +
   41.31 +data class IssueStatusPhase(val number: Int) {
   41.32 +    companion object {
   41.33 +        val Open = IssueStatusPhase(0)
   41.34 +        val WorkInProgress = IssueStatusPhase(1)
   41.35 +        val Done = IssueStatusPhase(2)
   41.36 +    }
   41.37 +}
   41.38 \ 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/types/VersionStatus.kt	Mon Dec 21 18:29:34 2020 +0100
    42.3 @@ -0,0 +1,32 @@
    42.4 +/*
    42.5 + * Copyright 2020 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.types
   42.30 +
   42.31 +enum class VersionStatus {
   42.32 +    Future, Unreleased, Released, LTS, Deprecated;
   42.33 +
   42.34 +    val isReleased get() = this.ordinal >= Released.ordinal
   42.35 +}
   42.36 \ No newline at end of file
    43.1 --- a/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp	Sun Dec 20 11:06:25 2020 +0100
    43.2 +++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp	Mon Dec 21 18:29:34 2020 +0100
    43.3 @@ -26,7 +26,7 @@
    43.4  --%>
    43.5  <%@page pageEncoding="UTF-8"
    43.6          import="de.uapcore.lightpit.viewmodel.ProjectView"
    43.7 -        import="de.uapcore.lightpit.entities.VersionStatus"
    43.8 +        import="de.uapcore.lightpit.types.VersionStatus"
    43.9  %>
   43.10  <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   43.11  <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

mercurial