Mon, 21 Dec 2020 18:29:34 +0100
major refactoring of DAO architecture - also fixes #114
--- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Mon Dec 21 18:29:34 2020 +0100 @@ -28,8 +28,8 @@ */ package de.uapcore.lightpit; -import de.uapcore.lightpit.dao.DaoProvider; -import de.uapcore.lightpit.dao.postgres.PGDaoProvider; +import de.uapcore.lightpit.dao.DataAccessObject; +import de.uapcore.lightpit.dao.PostgresDataAccessObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,26 +56,6 @@ private static final String SITE_JSP = jspPath("site"); - - @FunctionalInterface - protected interface SQLFindFunction<K, T> { - T apply(K key) throws SQLException; - - default <V> SQLFindFunction<V, T> compose(Function<? super V, ? extends K> before) throws SQLException { - Objects.requireNonNull(before); - return (v) -> this.apply(before.apply(v)); - } - - default <V> SQLFindFunction<K, V> andThen(Function<? super T, ? extends V> after) throws SQLException { - Objects.requireNonNull(after); - return (t) -> after.apply(this.apply(t)); - } - - static <K> Function<K, K> identity() { - return (t) -> t; - } - } - /** * Invocation mapping gathered from the {@link RequestMapping} annotations. * <p> @@ -101,15 +81,15 @@ * @param connection the SQL connection * @return a set of data access objects */ - private DaoProvider createDataAccessObjects(Connection connection) throws SQLException { + private DataAccessObject createDataAccessObjects(Connection connection) { final var df = (DataSourceProvider) getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME()); if (df.getDialect() == DataSourceProvider.Dialect.Postgres) { - return new PGDaoProvider(connection); + return new PostgresDataAccessObject(connection); } throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug."); } - private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException { + private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException { final var pathPattern = mapping.getKey(); final var method = mapping.getValue(); try { @@ -122,7 +102,7 @@ } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) { paramValues[i] = resp; } - if (paramTypes[i].isAssignableFrom(DaoProvider.class)) { + if (paramTypes[i].isAssignableFrom(DataAccessObject.class)) { paramValues[i] = dao; } if (paramTypes[i].isAssignableFrom(PathParameters.class)) { @@ -179,9 +159,9 @@ boolean paramsInjectible = true; for (var param : method.getParameterTypes()) { paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param) - || HttpServletResponse.class.isAssignableFrom(param) - || PathParameters.class.isAssignableFrom(param) - || DaoProvider.class.isAssignableFrom(param); + || HttpServletResponse.class.isAssignableFrom(param) + || PathParameters.class.isAssignableFrom(param) + || DataAccessObject.class.isAssignableFrom(param); } if (paramsInjectible) { try { @@ -360,7 +340,7 @@ * @return the retrieved entity or an empty optional if there is no such entity or the request parameter was missing * @throws SQLException if the find function throws an exception */ - protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, SQLFindFunction<? super T, ? extends R> find) throws SQLException { + protected <T, R> Optional<R> findByParameter(HttpServletRequest req, Class<T> clazz, String name, Function<? super T, ? extends R> find) { final var param = getParameter(req, clazz, name); if (param.isPresent()) { return Optional.ofNullable(find.apply(param.get()));
--- a/src/main/java/de/uapcore/lightpit/dao/Functions.java Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ -package de.uapcore.lightpit.dao; - -import java.sql.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -/** - * Some DAO utilities. - */ -public final class Functions { - - public static String getSafeString(ResultSet rs, String column) throws SQLException { - return Optional.ofNullable(rs.getString(column)).orElse(""); - } - - public static void setStringOrNull(PreparedStatement stmt, int index, String str) throws SQLException { - if (str == null || str.isBlank()) { - stmt.setNull(index, Types.VARCHAR); - } else { - stmt.setString(index, str); - } - } - - public static void setDateOrNull(PreparedStatement stmt, int index, Date date) throws SQLException { - if (date == null) { - stmt.setNull(index, Types.DATE); - } else { - stmt.setDate(index, date); - } - } - - public static <T> void setForeignKeyOrNull(PreparedStatement stmt, int index, T instance, Function<? super T, Integer> keyGetter) throws SQLException { - Integer key = Optional.ofNullable(instance).map(keyGetter).orElse(null); - if (key == null) { - stmt.setNull(index, Types.INTEGER); - } else { - stmt.setInt(index, key); - } - } - - @FunctionalInterface - public interface ResultSetMapper<T> { - T apply(ResultSet rs) throws SQLException; - } - - public static <T> List<T> list(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException { - List<T> results = new ArrayList<>(); - try (var result = stmt.executeQuery()) { - while (result.next()) { - final var project = mapper.apply(result); - results.add(project); - } - } - return results; - } - - public static <T> T find(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException { - try (var result = stmt.executeQuery()) { - if (result.next()) { - final var ent = mapper.apply(result); - return ent; - } else { - return null; - } - } - } - - private Functions() { - - } -}
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Mon Dec 21 18:29:34 2020 +0100 @@ -30,8 +30,15 @@ import de.uapcore.lightpit.*; -import de.uapcore.lightpit.dao.DaoProvider; +import de.uapcore.lightpit.dao.DataAccessObject; import de.uapcore.lightpit.entities.*; +import de.uapcore.lightpit.filter.AllFilter; +import de.uapcore.lightpit.filter.IssueFilter; +import de.uapcore.lightpit.filter.NoneFilter; +import de.uapcore.lightpit.filter.SpecificFilter; +import de.uapcore.lightpit.types.IssueCategory; +import de.uapcore.lightpit.types.IssueStatus; +import de.uapcore.lightpit.types.VersionStatus; import de.uapcore.lightpit.types.WebColor; import de.uapcore.lightpit.viewmodel.*; import de.uapcore.lightpit.viewmodel.util.IssueSorter; @@ -45,7 +52,6 @@ import java.io.IOException; import java.sql.Date; import java.sql.SQLException; -import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.stream.Collectors; @@ -72,25 +78,21 @@ } } - private void populate(ProjectView viewModel, PathParameters pathParameters, DaoProvider dao) { - final var projectDao = dao.getProjectDao(); - final var versionDao = dao.getVersionDao(); - final var componentDao = dao.getComponentDao(); - - projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add); + private void populate(ProjectView viewModel, PathParameters pathParameters, DataAccessObject dao) { + dao.listProjects().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add); if (pathParameters == null) return; // Select Project - final var project = projectDao.findByNode(pathParameters.get("project")); + final var project = dao.findProjectByNode(pathParameters.get("project")); if (project == null) return; final var info = new ProjectInfo(project); - info.setVersions(versionDao.list(project)); - info.setComponents(componentDao.list(project)); - info.setIssueSummary(projectDao.getIssueSummary(project)); + info.setVersions(dao.listVersions(project)); + info.setComponents(dao.listComponents(project)); + info.setIssueSummary(dao.collectIssueSummary(project)); viewModel.setProjectInfo(info); // Select Version @@ -101,7 +103,7 @@ } else if ("all-versions".equals(versionNode)) { viewModel.setVersionFilter(ProjectView.ALL_VERSIONS); } else { - viewModel.setVersionFilter(versionDao.findByNode(project, versionNode)); + viewModel.setVersionFilter(dao.findVersionByNode(project, versionNode)); } } @@ -113,7 +115,7 @@ } else if ("all-components".equals(componentNode)) { viewModel.setComponentFilter(ProjectView.ALL_COMPONENTS); } else { - viewModel.setComponentFilter(componentDao.findByNode(project, componentNode)); + viewModel.setComponentFilter(dao.findComponentByNode(project, componentNode)); } } } @@ -137,28 +139,25 @@ } @RequestMapping(method = HttpMethod.GET) - public void index(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException { + public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException { final var viewModel = new ProjectView(); populate(viewModel, null, dao); - final var projectDao = dao.getProjectDao(); - final var versionDao = dao.getVersionDao(); - for (var info : viewModel.getProjectList()) { - info.setVersions(versionDao.list(info.getProject())); - info.setIssueSummary(projectDao.getIssueSummary(info.getProject())); + info.setVersions(dao.listVersions(info.getProject())); + info.setIssueSummary(dao.collectIssueSummary(info.getProject())); } forwardView(req, resp, viewModel, "projects"); } - private void configureProjectEditor(ProjectEditView viewModel, Project project, DaoProvider dao) throws SQLException { + private void configureProjectEditor(ProjectEditView viewModel, Project project, DataAccessObject dao) { viewModel.setProject(project); - viewModel.setUsers(dao.getUserDao().list()); + viewModel.setUsers(dao.listUsers()); } @RequestMapping(requestPath = "$project/edit", method = HttpMethod.GET) - public void edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DaoProvider dao) throws IOException, SQLException, ServletException { + public void edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws IOException, SQLException, ServletException { final var viewModel = new ProjectEditView(); populate(viewModel, pathParams, dao); @@ -172,7 +171,7 @@ } @RequestMapping(requestPath = "create", method = HttpMethod.GET) - public void create(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException { + public void create(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { final var viewModel = new ProjectEditView(); populate(viewModel, null, dao); configureProjectEditor(viewModel, new Project(-1), dao); @@ -180,7 +179,7 @@ } @RequestMapping(requestPath = "commit", method = HttpMethod.POST) - public void commit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException { + public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { try { final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); @@ -195,12 +194,10 @@ ownerId -> ownerId >= 0 ? new User(ownerId) : null ).ifPresent(project::setOwner); - final var projectDao = dao.getProjectDao(); if (project.getId() > 0) { - // TODO: unused return value - projectDao.update(project); + dao.updateProject(project); } else { - projectDao.save(project); + dao.insertProject(project); } setRedirectLocation(req, "./projects/"); @@ -215,7 +212,7 @@ } @RequestMapping(requestPath = "$project/$component/$version/issues/", method = HttpMethod.GET) - public void issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DaoProvider dao) throws SQLException, IOException, ServletException { + public void issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObject dao) throws SQLException, IOException, ServletException { final var viewModel = new ProjectDetailsView(); populate(viewModel, pathParams, dao); @@ -228,36 +225,64 @@ final var version = viewModel.getVersionFilter(); final var component = viewModel.getComponentFilter(); - final var issueDao = dao.getIssueDao(); + // TODO: use new IssueFilter class for the ViewModel - final List<Issue> issues; + final var projectFilter = new SpecificFilter<>(project); + final IssueFilter filter; if (version.equals(ProjectView.NO_VERSION)) { if (component.equals(ProjectView.ALL_COMPONENTS)) { - issues = issueDao.list(project, (Version) null); + filter = new IssueFilter(projectFilter, + new NoneFilter<>(), + new AllFilter<>() + ); } else if (component.equals(ProjectView.NO_COMPONENT)) { - issues = issueDao.list(project, null, null); + filter = new IssueFilter(projectFilter, + new NoneFilter<>(), + new NoneFilter<>() + ); } else { - issues = issueDao.list(project, component, null); + filter = new IssueFilter(projectFilter, + new NoneFilter<>(), + new SpecificFilter<>(component) + ); } } else if (version.equals(ProjectView.ALL_VERSIONS)) { if (component.equals(ProjectView.ALL_COMPONENTS)) { - issues = issueDao.list(project); + filter = new IssueFilter(projectFilter, + new AllFilter<>(), + new AllFilter<>() + ); } else if (component.equals(ProjectView.NO_COMPONENT)) { - issues = issueDao.list(project, (Component)null); + filter = new IssueFilter(projectFilter, + new AllFilter<>(), + new NoneFilter<>() + ); } else { - issues = issueDao.list(project, component); + filter = new IssueFilter(projectFilter, + new AllFilter<>(), + new SpecificFilter<>(component) + ); } } else { if (component.equals(ProjectView.ALL_COMPONENTS)) { - issues = issueDao.list(project, version); + filter = new IssueFilter(projectFilter, + new SpecificFilter<>(version), + new AllFilter<>() + ); } else if (component.equals(ProjectView.NO_COMPONENT)) { - issues = issueDao.list(project, null, version); + filter = new IssueFilter(projectFilter, + new SpecificFilter<>(version), + new NoneFilter<>() + ); } else { - issues = issueDao.list(project, component, version); + filter = new IssueFilter(projectFilter, + new SpecificFilter<>(version), + new SpecificFilter<>(component) + ); } } - for (var issue : issues) issueDao.joinVersionInformation(issue); + final var issues = dao.listIssues(filter); issues.sort(new IssueSorter( new IssueSorter.Criteria(IssueSorter.Field.DONE, true), new IssueSorter.Criteria(IssueSorter.Field.ETA, true), @@ -273,7 +298,7 @@ } @RequestMapping(requestPath = "$project/versions/", method = HttpMethod.GET) - public void versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { final var viewModel = new VersionsView(); populate(viewModel, pathParameters, dao); @@ -283,16 +308,20 @@ return; } - final var issueDao = dao.getIssueDao(); - final var issues = issueDao.list(projectInfo.getProject()); - for (var issue : issues) issueDao.joinVersionInformation(issue); + final var issues = dao.listIssues( + new IssueFilter( + new SpecificFilter<>(projectInfo.getProject()), + new AllFilter<>(), + new AllFilter<>() + ) + ); viewModel.update(projectInfo.getVersions(), issues); forwardView(req, resp, viewModel, "versions"); } @RequestMapping(requestPath = "$project/versions/$version/edit", method = HttpMethod.GET) - public void editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { final var viewModel = new VersionEditView(); populate(viewModel, pathParameters, dao); @@ -307,7 +336,7 @@ } @RequestMapping(requestPath = "$project/create-version", method = HttpMethod.GET) - public void createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { final var viewModel = new VersionEditView(); populate(viewModel, pathParameters, dao); @@ -316,22 +345,22 @@ return; } - viewModel.setVersion(new Version(-1)); + viewModel.setVersion(new Version(-1, viewModel.getProjectInfo().getProject().getId())); forwardView(req, resp, viewModel, "version-form"); } @RequestMapping(requestPath = "commit-version", method = HttpMethod.POST) - public void commitVersion(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException { + public void commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { try { - final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow()); + final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); if (project == null) { // TODO: improve error handling, because not found is not correct for this POST request resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow()); + final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), project.getId()); version.setName(getParameter(req, String.class, "name").orElseThrow()); final var node = getParameter(req, String.class, "node").orElse(null); @@ -340,12 +369,10 @@ getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal); version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); - final var versionDao = dao.getVersionDao(); if (version.getId() > 0) { - // TODO: use return value - versionDao.update(version); + dao.updateVersion(version); } else { - versionDao.save(version, project); + dao.insertVersion(version); } setRedirectLocation(req, "./projects/" + project.getNode() + "/versions/"); @@ -359,7 +386,7 @@ } @RequestMapping(requestPath = "$project/components/", method = HttpMethod.GET) - public void components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { final var viewModel = new ComponentsView(); populate(viewModel, pathParameters, dao); @@ -369,15 +396,20 @@ return; } - final var issueDao = dao.getIssueDao(); - final var issues = issueDao.list(projectInfo.getProject()); + final var issues = dao.listIssues( + new IssueFilter( + new SpecificFilter<>(projectInfo.getProject()), + new AllFilter<>(), + new AllFilter<>() + ) + ); viewModel.update(projectInfo.getComponents(), issues); forwardView(req, resp, viewModel, "components"); } @RequestMapping(requestPath = "$project/components/$component/edit", method = HttpMethod.GET) - public void editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { final var viewModel = new ComponentEditView(); populate(viewModel, pathParameters, dao); @@ -387,13 +419,13 @@ } viewModel.setComponent(viewModel.getComponentFilter()); - viewModel.setUsers(dao.getUserDao().list()); + viewModel.setUsers(dao.listUsers()); forwardView(req, resp, viewModel, "component-form"); } @RequestMapping(requestPath = "$project/create-component", method = HttpMethod.GET) - public void createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { final var viewModel = new ComponentEditView(); populate(viewModel, pathParameters, dao); @@ -402,23 +434,23 @@ return; } - viewModel.setComponent(new Component(-1)); - viewModel.setUsers(dao.getUserDao().list()); + viewModel.setComponent(new Component(-1, viewModel.getProjectInfo().getProject().getId())); + viewModel.setUsers(dao.listUsers()); forwardView(req, resp, viewModel, "component-form"); } @RequestMapping(requestPath = "commit-component", method = HttpMethod.POST) - public void commitComponent(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException { + public void commitComponent(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { try { - final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow()); + final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); if (project == null) { // TODO: improve error handling, because not found is not correct for this POST request resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow()); + final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow(), project.getId()); component.setName(getParameter(req, String.class, "name").orElseThrow()); final var node = getParameter(req, String.class, "node").orElse(null); @@ -431,12 +463,10 @@ ).ifPresent(component::setLead); getParameter(req, String.class, "description").ifPresent(component::setDescription); - final var componentDao = dao.getComponentDao(); if (component.getId() > 0) { - // TODO: use return value - componentDao.update(component); + dao.updateComponent(component); } else { - componentDao.save(component, project); + dao.insertComponent(component); } setRedirectLocation(req, "./projects/" + project.getNode() + "/components/"); @@ -449,17 +479,17 @@ } } - private void configureIssueEditor(IssueEditView viewModel, Issue issue, DaoProvider dao) throws SQLException { + private void configureIssueEditor(IssueEditView viewModel, Issue issue, DataAccessObject dao) { final var project = viewModel.getProjectInfo().getProject(); issue.setProject(project); // automatically set current project for new issues viewModel.setIssue(issue); viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions()); - viewModel.setUsers(dao.getUserDao().list()); - viewModel.setComponents(dao.getComponentDao().list(project)); + viewModel.setUsers(dao.listUsers()); + viewModel.setComponents(dao.listComponents(project)); } @RequestMapping(requestPath = "$project/issues/$issue/view", method = HttpMethod.GET) - public void viewIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void viewIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, ServletException { final var viewModel = new IssueDetailView(); populate(viewModel, pathParameters, dao); @@ -469,16 +499,14 @@ return; } - final var issueDao = dao.getIssueDao(); - final var issue = issueDao.find(parseIntOrZero(pathParameters.get("issue"))); + final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue"))); if (issue == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - issueDao.joinVersionInformation(issue); viewModel.setIssue(issue); - viewModel.setComments(issueDao.listComments(issue)); + viewModel.setComments(dao.listComments(issue)); viewModel.processMarkdown(); @@ -487,7 +515,7 @@ // TODO: why should the issue editor be child of $project? @RequestMapping(requestPath = "$project/issues/$issue/edit", method = HttpMethod.GET) - public void editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { final var viewModel = new IssueEditView(); populate(viewModel, pathParameters, dao); @@ -497,21 +525,19 @@ return; } - final var issueDao = dao.getIssueDao(); - final var issue = issueDao.find(parseIntOrZero(pathParameters.get("issue"))); + final var issue = dao.findIssue(parseIntOrZero(pathParameters.get("issue"))); if (issue == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - issueDao.joinVersionInformation(issue); configureIssueEditor(viewModel, issue, dao); forwardView(req, resp, viewModel, "issue-form"); } @RequestMapping(requestPath = "$project/create-issue", method = HttpMethod.GET) - public void createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DaoProvider dao) throws IOException, SQLException, ServletException { + public void createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObject dao) throws IOException, SQLException, ServletException { final var viewModel = new IssueEditView(); populate(viewModel, pathParameters, dao); @@ -521,7 +547,8 @@ return; } - final var issue = new Issue(-1); + // TODO: fix #38 - automatically select component (and version) + final var issue = new Issue(-1, projectInfo.getProject(), null); issue.setProject(projectInfo.getProject()); configureIssueEditor(viewModel, issue, dao); @@ -529,27 +556,25 @@ } @RequestMapping(requestPath = "commit-issue", method = HttpMethod.POST) - public void commitIssue(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException { + public void commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { try { - final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow()); - final var componentId = getParameter(req, Integer.class, "component"); - final Component component; - if (componentId.isPresent()) { - component = dao.getComponentDao().find(componentId.get()); - } else { - component = null; - } - final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow()); + final var project = dao.findProject(getParameter(req, Integer.class, "pid").orElseThrow()); if (project == null) { // TODO: improve error handling, because not found is not correct for this POST request resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - issue.setProject(project); + final var componentId = getParameter(req, Integer.class, "component"); + final Component component; + if (componentId.isPresent()) { + component = dao.findComponent(componentId.get()); + } else { + component = null; + } + final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), project, component); getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory); getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus); issue.setSubject(getParameter(req, String.class, "subject").orElseThrow()); - issue.setComponent(component); getParameter(req, Integer.class, "assignee").map(userid -> { if (userid >= 0) { return new User(userid); @@ -566,23 +591,23 @@ getParameter(req, Integer[].class, "affected") .map(Stream::of) .map(stream -> - stream.map(Version::new).collect(Collectors.toList()) + stream.map(id -> new Version(id, project.getId())) + .collect(Collectors.toList()) ).ifPresent(issue::setAffectedVersions); getParameter(req, Integer[].class, "resolved") .map(Stream::of) .map(stream -> - stream.map(Version::new).collect(Collectors.toList()) + stream.map(id -> new Version(id, project.getId())) + .collect(Collectors.toList()) ).ifPresent(issue::setResolvedVersions); - final var issueDao = dao.getIssueDao(); if (issue.getId() > 0) { - // TODO: use return value - issueDao.update(issue); + dao.updateIssue(issue); } else { - issueDao.save(issue, project); + dao.insertIssue(issue); } - // TODO: fix redirect location + // TODO: implement #110 setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view"); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); @@ -594,19 +619,19 @@ } @RequestMapping(requestPath = "commit-issue-comment", method = HttpMethod.POST) - public void commentIssue(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws IOException, ServletException { + public void commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws IOException, ServletException { final var issueIdParam = getParameter(req, Integer.class, "issueid"); if (issueIdParam.isEmpty()) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Detected manipulated form."); return; } - final var issue = dao.getIssueDao().find(issueIdParam.get()); + final var issue = dao.findIssue(issueIdParam.get()); if (issue == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } try { - final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1)); + final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue.getId()); issueComment.setComment(getParameter(req, String.class, "comment").orElse("")); if (issueComment.getComment().isBlank()) { @@ -615,12 +640,11 @@ LOG.debug("User {} is commenting on issue #{}", req.getRemoteUser(), issue.getId()); if (req.getRemoteUser() != null) { - Optional.ofNullable(dao.getUserDao().findByUsername(req.getRemoteUser())).ifPresent(issueComment::setAuthor); + Optional.ofNullable(dao.findUserByName(req.getRemoteUser())).ifPresent(issueComment::setAuthor); } - dao.getIssueDao().saveComment(issue, issueComment); + dao.insertComment(issueComment); - // TODO: fix redirect location setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view"); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
--- a/src/main/java/de/uapcore/lightpit/modules/UsersModule.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/modules/UsersModule.java Mon Dec 21 18:29:34 2020 +0100 @@ -32,7 +32,7 @@ import de.uapcore.lightpit.Constants; import de.uapcore.lightpit.HttpMethod; import de.uapcore.lightpit.RequestMapping; -import de.uapcore.lightpit.dao.DaoProvider; +import de.uapcore.lightpit.dao.DataAccessObject; import de.uapcore.lightpit.entities.User; import de.uapcore.lightpit.viewmodel.UsersEditView; import de.uapcore.lightpit.viewmodel.UsersView; @@ -61,11 +61,9 @@ } @RequestMapping(method = HttpMethod.GET) - public void index(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException { - final var userDao = dao.getUserDao(); - + public void index(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { final var viewModel = new UsersView(); - viewModel.setUsers(userDao.list()); + viewModel.setUsers(dao.listUsers()); setViewModel(req, viewModel); setContentPage(req, "users"); @@ -73,11 +71,10 @@ } @RequestMapping(requestPath = "edit", method = HttpMethod.GET) - public void edit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws SQLException, ServletException, IOException { + public void edit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws SQLException, ServletException, IOException { final var viewModel = new UsersEditView(); - viewModel.setUser(findByParameter(req, Integer.class, "id", - dao.getUserDao()::find).orElse(new User(-1))); + viewModel.setUser(findByParameter(req, Integer.class, "id", dao::findUser).orElse(new User(-1))); setViewModel(req, viewModel); setContentPage(req, "user-form"); @@ -86,7 +83,7 @@ } @RequestMapping(requestPath = "commit", method = HttpMethod.POST) - public void commit(HttpServletRequest req, HttpServletResponse resp, DaoProvider dao) throws ServletException, IOException { + public void commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObject dao) throws ServletException, IOException { User user = new User(-1); try { @@ -96,12 +93,10 @@ getParameter(req, String.class, "lastname").ifPresent(user::setLastname); getParameter(req, String.class, "mail").ifPresent(user::setMail); - final var userDao = dao.getUserDao(); if (user.getId() > 0) { - // TODO: unused return value - userDao.update(user); + dao.updateUser(user); } else { - userDao.save(user); + dao.insertUser(user); } setRedirectLocation(req, "./teams/");
--- a/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java Mon Dec 21 18:29:34 2020 +0100 @@ -1,6 +1,12 @@ package de.uapcore.lightpit.viewmodel; -import de.uapcore.lightpit.entities.*; +import de.uapcore.lightpit.entities.Component; +import de.uapcore.lightpit.entities.Project; +import de.uapcore.lightpit.entities.User; +import de.uapcore.lightpit.entities.Version; +import de.uapcore.lightpit.types.IssueCategory; +import de.uapcore.lightpit.types.IssueStatus; +import de.uapcore.lightpit.types.VersionStatus; import java.util.*;
--- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java Mon Dec 21 18:29:34 2020 +0100 @@ -12,10 +12,12 @@ public static final int SELECTED_PAGE_VERSIONS = 1; public static final int SELECTED_PAGE_COMPONENTS = 2; - public static final Version ALL_VERSIONS = new Version(0); - public static final Version NO_VERSION = new Version(-1); - public static final Component ALL_COMPONENTS = new Component(0); - public static final Component NO_COMPONENT = new Component(-1); + // TODO: use new Filter class + + public static final Version ALL_VERSIONS = new Version(0,0); + public static final Version NO_VERSION = new Version(-1,0); + public static final Component ALL_COMPONENTS = new Component(0,0); + public static final Component NO_COMPONENT = new Component(-1,0); static { ALL_VERSIONS.setNode("all-versions");
--- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java Mon Dec 21 18:29:34 2020 +0100 @@ -1,7 +1,7 @@ package de.uapcore.lightpit.viewmodel; import de.uapcore.lightpit.entities.Version; -import de.uapcore.lightpit.entities.VersionStatus; +import de.uapcore.lightpit.types.VersionStatus; public class VersionEditView extends ProjectView { private Version version;
--- a/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java Mon Dec 21 18:29:34 2020 +0100 @@ -1,7 +1,7 @@ package de.uapcore.lightpit.viewmodel.util; import de.uapcore.lightpit.entities.Issue; -import de.uapcore.lightpit.entities.IssueStatusPhase; +import de.uapcore.lightpit.types.IssueStatusPhase; import java.util.Arrays; import java.util.Comparator;
--- a/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt Mon Dec 21 18:29:34 2020 +0100 @@ -128,7 +128,7 @@ val sc = sce!!.servletContext val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA - sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let {dbDialect -> + sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let { dbDialect -> try { dialect = Dialect.valueOf(dbDialect) } catch (ex: IllegalArgumentException) {
--- a/src/main/kotlin/de/uapcore/lightpit/Logging.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/Logging.kt Mon Dec 21 18:29:34 2020 +0100 @@ -29,4 +29,5 @@ import org.slf4j.LoggerFactory interface LoggingTrait -inline fun <reified T : LoggingTrait> T.logger(): Logger = LoggerFactory.getLogger(T::class.java); + +inline fun <reified T : LoggingTrait> T.logger(): Logger = LoggerFactory.getLogger(T::class.java)
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractChildEntityDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - - -abstract class AbstractChildEntityDao<T, P> : AbstractDao<T>() { - - /** - * Lists all entities being a child of the specified parent. - * @param parent the parent - * @return the list of child instances - */ - abstract fun list(parent: P): List<T> - - /** - * Finds an entity by its integer ID. - * It is not guaranteed that referenced entities are automatically joined. - * - * @param id the id - * @return the entity or null if there is no such entity - */ - abstract fun find(id: Int): T? - - /** - * Inserts an instance into database. - * It is not guaranteed that generated fields will be updated in the instance. - * - * @param instance the instance to insert - * @param parent a reference to the parent - */ - abstract fun save(instance: T, parent: P) - - /** - * Updates an instance in the database. - * - * @param instance the instance to update - * @return true if an instance has been updated, false if the instance is not present in database - */ - abstract fun update(instance: T): Boolean -}
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractComponentDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import de.uapcore.lightpit.entities.Component -import de.uapcore.lightpit.entities.Project - -abstract class AbstractComponentDao : AbstractChildEntityDao<Component, Project>() { - abstract fun findByNode(parent: Project, node: String): Component? -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import java.sql.PreparedStatement -import java.sql.ResultSet -import java.sql.Types - -abstract class AbstractDao<T> { - - abstract fun mapResult(rs: ResultSet): T - - protected fun list(stmt: PreparedStatement): List<T> { - return sequence { - stmt.executeQuery().use { result -> - while (result.next()) yield(mapResult(result)) - } - }.toList() - } - - protected fun find(stmt: PreparedStatement): T? { - stmt.executeQuery().use { result -> - return if (result.next()) { - mapResult(result) - } else { - null - } - } - } - - // TODO: create PreparedStatement abstraction that provides some features - - // TODO: remove the following legacy code helper function - protected fun <T> setForeignKeyOrNull(stmt: PreparedStatement, index: Int, instance: T?, keyGetter: (obj: T) -> Int) { - if (instance == null) { - stmt.setNull(index, Types.INTEGER) - } else { - stmt.setInt(index, keyGetter(instance)) - } - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractEntityDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -abstract class AbstractEntityDao<T> : AbstractDao<T>() { - - /** - * Lists all entities. - * @return a list of all entities - */ - abstract fun list(): List<T> - - /** - * Finds an entity by its integer ID. - * It is not guaranteed that referenced entities are automatically joined. - * - * @param id the id - * @return the entity or null if there is no such entity - */ - abstract fun find(id: Int): T? - - /** - * Inserts an instance into database. - * It is not guaranteed that generated fields will be updated in the instance. - * - * @param instance the instance to insert - */ - abstract fun save(instance: T) - - /** - * Updates an instance in the database. - * - * @param instance the instance to update - * @return true if an instance has been updated, false if the instance is not present in database - */ - abstract fun update(instance: T): Boolean -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractIssueDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import de.uapcore.lightpit.entities.* -import java.sql.SQLException - -abstract class AbstractIssueDao : AbstractChildEntityDao<Issue, Project>() { - - /** - * Lists all issues that are related to the specified component and version. - * If component or version is null, search for issues that are not assigned to any - * component or version, respectively. - * - * @param project the project - * @param component the component - * @param version the version - * @return a list of issues - */ - abstract fun list(project: Project, component: Component?, version: Version?): List<Issue> - - /** - * Lists all issues that are related to the specified version. - * If the version is null, lists issues that are not assigned to any version. - * - * @param project the project - * @param version the version or null - * @return a list of issues - */ - abstract fun list(project: Project, version: Version?): List<Issue> - - /** - * Lists all issues that are related to the specified component. - * If the component is null, lists issues that are not assigned to a component. - * - * @param project the project - * @param component the component or null - * @return a list of issues - */ - abstract fun list(project: Project, component: Component?): List<Issue> - - /** - * Lists all comments for a specific issue in chronological order. - * - * @param issue the issue - * @return the list of comments - */ - abstract fun listComments(issue: Issue): List<IssueComment> - - /** - * Stores the specified comment in database. - * This is an update-or-insert operation. - * The "updated" date of the corresponding issue is also updated. - * - * @param issue the issue to save the comment for - * @param comment the comment to save - */ - abstract fun saveComment(issue: Issue, comment: IssueComment) - - /** - * Saves an instances to the database. - * Implementations of this DAO must guarantee that the generated ID is stored in the instance. - * - * @param instance the instance to insert - * @param parent the parent project - * @throws SQLException on any kind of SQL error - */ - abstract override fun save(instance: Issue, parent: Project) - - /** - * Retrieves the affected, scheduled and resolved versions for the specified issue. - * - * @param issue the issue to join the information for - */ - abstract fun joinVersionInformation(issue: Issue) -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractProjectDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import de.uapcore.lightpit.entities.IssueSummary -import de.uapcore.lightpit.entities.Project - -abstract class AbstractProjectDao : AbstractEntityDao<Project>() { - - abstract fun getIssueSummary(project: Project): IssueSummary - - abstract fun findByNode(node: String): Project? -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractUserDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import de.uapcore.lightpit.entities.User - -abstract class AbstractUserDao : AbstractEntityDao<User>() { - abstract fun findByUsername(username: String): User? -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/AbstractVersionDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -import de.uapcore.lightpit.entities.Project -import de.uapcore.lightpit.entities.Version - -abstract class AbstractVersionDao : AbstractChildEntityDao<Version, Project>() { - abstract fun findByNode(parent: Project, node: String): Version? -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/DaoProvider.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao - -interface DaoProvider { - val userDao: AbstractUserDao - val projectDao: AbstractProjectDao - val componentDao: AbstractComponentDao - val versionDao: AbstractVersionDao - val issueDao: AbstractIssueDao -} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.dao + +import de.uapcore.lightpit.entities.* +import de.uapcore.lightpit.filter.IssueFilter + +interface DataAccessObject { + fun listUsers(): List<User> + fun findUser(id: Int): User? + fun findUserByName(username: String): User? + fun insertUser(user: User) + fun updateUser(user: User) + + fun listVersions(project: Project): List<Version> + fun findVersion(id: Int): Version? + fun findVersionByNode(project: Project, node: String): Version? + fun insertVersion(version: Version) + fun updateVersion(version: Version) + + fun listComponents(project: Project): List<Component> + fun findComponent(id: Int): Component? + fun findComponentByNode(project: Project, node: String): Component? + fun insertComponent(component: Component) + fun updateComponent(component: Component) + + fun listProjects(): List<Project> + fun findProject(id: Int): Project? + fun findProjectByNode(node: String): Project? + fun insertProject(project: Project) + fun updateProject(project: Project) + + fun collectIssueSummary(project: Project): IssueSummary + + fun listIssues(filter: IssueFilter): List<Issue> + fun findIssue(id: Int): Issue? + fun insertIssue(issue: Issue) + fun updateIssue(issue: Issue) + + fun listComments(issue: Issue): List<IssueComment> + fun insertComment(issueComment: IssueComment) +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/Extensions.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.dao + +import java.sql.Date +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.Types + +fun PreparedStatement.setStringSafe(idx: Int, str: String) { + setString(idx, str) +} + +fun PreparedStatement.setStringOrNull(idx: Int, str: String?) { + when (str) { + null -> setNull(idx, Types.VARCHAR) + else -> setString(idx, str) + } +} + +fun PreparedStatement.setIntOrNull(idx: Int, value: Int?) { + when (value) { + null -> setNull(idx, Types.INTEGER) + else -> setInt(idx, value) + } +} + +fun PreparedStatement.setDateOrNull(idx: Int, value: Date?) { + when (value) { + null -> setNull(idx, Types.DATE) + else -> setDate(idx, value) + } +} + +fun <T : Enum<T>> PreparedStatement.setEnum(idx: Int, e: Enum<T>) { + setString(idx, e.name) +} + +inline fun <reified T : Enum<T>> ResultSet.getEnum(col: String): T { + return enumValueOf(getString(col)) +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,739 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.dao + +import de.uapcore.lightpit.entities.* +import de.uapcore.lightpit.filter.* +import de.uapcore.lightpit.types.WebColor +import java.sql.Connection +import java.sql.PreparedStatement +import java.sql.ResultSet + +class PostgresDataAccessObject(private val connection: Connection) : DataAccessObject { + + //<editor-fold desc="User"> + private fun selectUserInfo( + rs: ResultSet, + idColumn: String = "userid", + usernameColumn: String = "username", + givennameColumn: String = "givenname", + lastnameColumn: String = "lastname", + mailColumn: String = "mail" + ): User? { + val idval = rs.getInt(idColumn) + return if (rs.wasNull()) null else { + User(idval).apply { + username = rs.getString(usernameColumn) + givenname = rs.getString(givennameColumn) + lastname = rs.getString(lastnameColumn) + mail = rs.getString(mailColumn) + } + } + } + + private fun selectUsers(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) selectUserInfo(rs)?.let { yield(it) } + } + } + + //language=SQL + private val userQuery = "select userid, username, lastname, givenname, mail from lpit_user" + + private val stmtUsers by lazy { + connection.prepareStatement( + """${userQuery} + where userid > 0 + order by username + """ + ) + } + private val stmtUserByID by lazy { + connection.prepareStatement( + """${userQuery} + where userid = ? + """ + ) + } + private val stmtUserByName by lazy { + connection.prepareStatement( + """${userQuery} + where lower(username) = lower(?) + """ + ) + } + private val stmtInsertUser by lazy { + connection.prepareStatement( + "insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)" + ) + } + private val stmtUpdateUser by lazy { + connection.prepareStatement( + "update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?" + ) + } + + override fun listUsers() = selectUsers(stmtUsers).toList() + override fun findUser(id: Int): User? { + stmtUserByID.setInt(1, id) + return selectUsers(stmtUserByID).firstOrNull() + } + + override fun findUserByName(username: String): User? { + stmtUserByName.setString(1, username) + return selectUsers(stmtUserByName).firstOrNull() + } + + override fun insertUser(user: User) { + with(user) { + stmtInsertUser.setStringSafe(1, username) + stmtInsertUser.setStringOrNull(2, lastname) + stmtInsertUser.setStringOrNull(3, givenname) + stmtInsertUser.setStringOrNull(4, mail) + } + stmtInsertUser.execute() + } + + override fun updateUser(user: User) { + with(user) { + stmtUpdateUser.setStringOrNull(1, lastname) + stmtUpdateUser.setStringOrNull(2, givenname) + stmtUpdateUser.setStringOrNull(3, mail) + stmtUpdateUser.setInt(4, id) + } + stmtUpdateUser.execute() + } + //</editor-fold> + + //<editor-fold desc="Version"> + private fun selectVersions(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) { + yield(Version(rs.getInt("versionid"), rs.getInt("project")).apply { + name = rs.getString("name") + node = rs.getString("node") + ordinal = rs.getInt("ordinal") + status = rs.getEnum("status") + }) + } + } + } + + private fun setVersionFields(stmt: PreparedStatement, obj: Version): Int { + with(obj) { + stmt.setStringSafe(1, name) + stmt.setStringSafe(2, node) + stmt.setInt(3, ordinal) + stmt.setEnum(4, status) + } + return 5 + } + + //language=SQL + private val versionQuery = "select versionid, project, name, node, ordinal, status from lpit_version" + + private val stmtVersions by lazy { + connection.prepareStatement( + """${versionQuery} + where project = ? + order by ordinal desc, lower(name) desc + """ + ) + } + private val stmtVersionByID by lazy { + connection.prepareStatement( + """${versionQuery} + where versionid = ? + """ + ) + } + private val stmtVersionByNode by lazy { + connection.prepareStatement( + """${versionQuery} + where project = ? and node = ? + """ + ) + } + private val stmtInsertVersion by lazy { + connection.prepareStatement( + """ + insert into lpit_version (name, node, ordinal, status, project) + values (?, ?, ?, ?::version_status, ?) + """ + ) + } + private val stmtUpdateVersion by lazy { + connection.prepareStatement( + """ + update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status + where versionid = ? + """ + ) + } + + override fun listVersions(project: Project): List<Version> { + stmtVersions.setInt(1, project.id) + return selectVersions(stmtVersions).toList() + } + + override fun findVersion(id: Int): Version? { + stmtVersionByID.setInt(1, id) + return selectVersions(stmtVersionByID).firstOrNull() + } + + override fun findVersionByNode(project: Project, node: String): Version? { + stmtVersionByNode.setInt(1, project.id) + stmtVersionByNode.setString(2, node) + return selectVersions(stmtVersionByNode).firstOrNull() + } + + override fun insertVersion(version: Version) { + val col = setVersionFields(stmtInsertVersion, version) + stmtInsertVersion.setInt(col, version.projectid) + stmtInsertVersion.execute() + } + + override fun updateVersion(version: Version) { + val col = setVersionFields(stmtUpdateVersion, version) + stmtUpdateVersion.setInt(col, version.id) + stmtUpdateVersion.execute() + } + //</editor-fold> + + //<editor-fold desc="Component"> + private fun selectComponents(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) { + yield(Component(rs.getInt("id"), rs.getInt("project")).apply { + name = rs.getString("name") + node = rs.getString("node") + color = try { + WebColor(rs.getString("color")) + } catch (ex: IllegalArgumentException) { + WebColor("000000") + } + ordinal = rs.getInt("ordinal") + description = rs.getString("description") + lead = selectUserInfo(rs) + }) + } + } + } + + private fun setComponentFields(stmt: PreparedStatement, obj: Component): Int { + with(obj) { + stmt.setStringSafe(1, name) + stmt.setStringSafe(2, node) + stmt.setStringSafe(3, color.hex) + stmt.setInt(4, ordinal) + stmt.setStringOrNull(5, description) + stmt.setIntOrNull(6, obj.lead?.id) + } + return 7 + } + + //language=SQL + private val componentQuery = + """ + select id, project, name, node, color, ordinal, description, + userid, username, givenname, lastname, mail + from lpit_component + left join lpit_user on lead = userid + """ + + private val stmtComponents by lazy { + connection.prepareStatement( + """${componentQuery} + where project = ? + order by ordinal, lower(name) + """ + ) + } + private val stmtComponentById by lazy { + connection.prepareStatement( + """${componentQuery} + where id = ? + """ + ) + } + private val stmtComponentByNode by lazy { + connection.prepareStatement( + """${componentQuery} + where project = ? and node = ? + """ + ) + } + private val stmtInsertComponent by lazy { + connection.prepareStatement( + """ + insert into lpit_component (name, node, color, ordinal, description, lead, project) + values (?, ?, ?, ?, ?, ?, ?) + """ + ) + } + private val stmtUpdateComponent by lazy { + connection.prepareStatement( + "update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?" + ) + } + + override fun listComponents(project: Project): List<Component> { + stmtComponents.setInt(1, project.id) + return selectComponents(stmtComponents).toList() + } + + override fun findComponent(id: Int): Component? { + stmtComponentById.setInt(1, id) + return selectComponents(stmtComponentById).firstOrNull() + } + + override fun findComponentByNode(project: Project, node: String): Component? { + stmtComponentByNode.setInt(1, project.id) + stmtComponentByNode.setString(2, node) + return selectComponents(stmtComponentByNode).firstOrNull() + } + + override fun insertComponent(component: Component) { + val col = setComponentFields(stmtInsertComponent, component) + stmtInsertComponent.setInt(col, component.projectid) + stmtInsertComponent.execute() + } + + override fun updateComponent(component: Component) { + val col = setComponentFields(stmtUpdateComponent, component) + stmtUpdateComponent.setInt(col, component.id) + stmtUpdateComponent.execute() + } + + //</editor-fold> + + //<editor-fold desc="Project"> + + private fun selectProjects(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) { + yield(Project(rs.getInt("projectid")).apply { + name = rs.getString("name") + node = rs.getString("node") + description = rs.getString("description") + repoUrl = rs.getString("repourl") + owner = selectUserInfo(rs) + }) + } + } + } + + private fun setProjectFields(stmt: PreparedStatement, obj: Project): Int { + with(obj) { + stmt.setStringSafe(1, name) + stmt.setStringSafe(2, node) + stmt.setStringOrNull(3, description) + stmt.setStringOrNull(4, repoUrl) + stmt.setIntOrNull(5, owner?.id) + } + return 6 + } + + //language=SQL + private val projectQuery = + """ + select projectid, name, node, description, repourl, + userid, username, lastname, givenname, mail + from lpit_project + left join lpit_user owner on lpit_project.owner = owner.userid + """ + + private val stmtProjects by lazy { + connection.prepareStatement( + """${projectQuery} + order by lower(name) + """ + ) + } + private val stmtProjectByID by lazy { + connection.prepareStatement( + """${projectQuery} + where projectid = ? + """ + ) + } + private val stmtProjectByNode by lazy { + connection.prepareStatement( + """${projectQuery} + where node = ? + """ + ) + } + private val stmtInsertProject by lazy { + connection.prepareStatement( + "insert into lpit_project (name, node, description, repourl, owner) values (?, ?, ?, ?, ?)" + ) + } + private val stmtUpdateProject by lazy { + connection.prepareStatement( + "update lpit_project set name = ?, node = ?, description = ?, repourl = ?, owner = ? where projectid = ?" + ) + } + private val stmtIssueSummary by lazy { + connection.prepareStatement( + """ + select phase, count(*) as total + from lpit_issue + join lpit_issue_phases using(status) + where project = ? + group by phase + """ + ) + } + + override fun listProjects(): List<Project> { + return selectProjects(stmtProjects).toList() + } + + override fun findProject(id: Int): Project? { + stmtProjectByID.setInt(1, id) + return selectProjects(stmtProjectByID).firstOrNull() + } + + override fun findProjectByNode(node: String): Project? { + stmtProjectByNode.setString(1, node) + return selectProjects(stmtProjectByNode).firstOrNull() + } + + override fun insertProject(project: Project) { + setProjectFields(stmtInsertProject, project) + stmtInsertProject.execute() + } + + override fun updateProject(project: Project) { + val col = setProjectFields(stmtUpdateProject, project) + stmtUpdateProject.setInt(col, project.id) + stmtUpdateProject.execute() + } + + override fun collectIssueSummary(project: Project): IssueSummary { + stmtIssueSummary.setInt(1, project.id) + return stmtIssueSummary.executeQuery().use { rs -> + val summary = IssueSummary() + while (rs.next()) { + val phase = rs.getInt("phase") + val total = rs.getInt("total") + when (phase) { + 0 -> summary.open = total + 1 -> summary.active = total + 2 -> summary.done = total + } + } + summary + } + } + + //</editor-fold> + + //<editor-fold desc="Issue"> + + private fun selectIssues(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) { + val proj = Project(rs.getInt("project")).apply { + name = rs.getString("projectname") + node = rs.getString("projectnode") + } + val comp = rs.getInt("component").let { + if (rs.wasNull()) null else + Component(it, proj.id).apply { + name = rs.getString("componentname") + node = rs.getString("componentnode") + } + } + val issue = Issue(rs.getInt("issueid"), proj, comp).apply { + component = comp + status = rs.getEnum("status") + category = rs.getEnum("category") + subject = rs.getString("subject") + description = rs.getString("description") + assignee = selectUserInfo(rs) + created = rs.getTimestamp("created") + updated = rs.getTimestamp("updated") + eta = rs.getDate("eta") + } + queryAffectedVersions.setInt(1, issue.id) + issue.affectedVersions = selectVersions(queryAffectedVersions).toList() + queryResolvedVersions.setInt(1, issue.id) + issue.resolvedVersions = selectVersions(queryResolvedVersions).toList() + yield(issue) + } + } + } + + private fun setIssueFields(stmt: PreparedStatement, obj: Issue): Int { + with(obj) { + stmt.setIntOrNull(1, component?.id) + stmt.setEnum(2, status) + stmt.setEnum(3, category) + stmt.setStringSafe(4, subject) + stmt.setStringOrNull(5, description) + stmt.setIntOrNull(6, assignee?.id) + stmt.setDateOrNull(7, eta) + } + return 8 + } + + //language=SQL + private val issueQuery = + """ + select issueid, + i.project, p.name as projectname, p.node as projectnode, + component, c.name as componentname, c.node as componentnode, + status, category, subject, i.description, + userid, username, givenname, lastname, mail, + created, updated, eta + from lpit_issue i + join lpit_project p on i.project = projectid + left join lpit_component c on component = c.id + left join lpit_user on userid = assignee + """ + + private val queryResolvedVersions by lazy { + connection.prepareStatement( + """ + select versionid, project, name, status, ordinal, node + from lpit_version v join lpit_issue_resolved_version using (versionid) + where issueid = ? + order by ordinal, name + """ + ) + } + + private val queryAffectedVersions by lazy { + connection.prepareStatement( + """ + select versionid, project, name, status, ordinal, node + from lpit_version join lpit_issue_affected_version using (versionid) + where issueid = ? + order by ordinal, name + """ + ) + } + + private val stmtIssues by lazy { + connection.prepareStatement( + """ + with issue_version as ( + select issueid, versionid from lpit_issue_affected_version + union select issueid, versionid from lpit_issue_resolved_version + ) ${issueQuery} left join issue_version using (issueid) + where + (not ? or projectid = ?) and + (not ? or versionid = ?) and (not ? or versionid is null) and + (not ? or component = ?) and (not ? or component is null) + """ + ) + } + + private val fproj = 1 + private val projectid = 2 + private val fversion = 3 + private val versionid = 4 + private val nversion = 5 + private val fcomp = 6 + private val component = 7 + private val ncomp = 8 + + private fun <T : Entity> applyFilter(filter: Filter<T>, fflag: Int, nflag: Int, idcol: Int) { + when (filter) { + is AllFilter -> { + stmtIssues.setBoolean(fflag, false) + stmtIssues.setBoolean(nflag, false) + stmtIssues.setInt(idcol, 0) + } + is NoneFilter -> { + stmtIssues.setBoolean(fflag, false) + stmtIssues.setBoolean(nflag, true) + stmtIssues.setInt(idcol, 0) + } + is SpecificFilter -> { + stmtIssues.setBoolean(fflag, true) + stmtIssues.setBoolean(nflag, false) + stmtIssues.setInt(idcol, filter.obj.id) + } + else -> { + TODO("Implement range filter.") + } + } + } + + override fun listIssues(filter: IssueFilter): List<Issue> { + when (filter.project) { + is AllFilter -> { + stmtIssues.setBoolean(fproj, false) + stmtIssues.setInt(projectid, 0) + } + is SpecificFilter -> { + stmtIssues.setBoolean(fproj, true) + stmtIssues.setInt(projectid, filter.project.obj.id) + } + else -> throw IllegalArgumentException() + } + applyFilter(filter.version, fversion, nversion, versionid) + applyFilter(filter.component, fcomp, ncomp, component) + + return selectIssues(stmtIssues).toList() + } + + private val stmtFindIssueByID by lazy { + connection.prepareStatement( + """${issueQuery} + where issueid = ? + """ + ) + } + private val stmtInsertIssue by lazy { + connection.prepareStatement( + """ + insert into lpit_issue (component, status, category, subject, description, assignee, eta, project) + values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?) + returning issueid + """ + ) + } + private val stmtUpdateIssue by lazy { + connection.prepareStatement( + """ + update lpit_issue set updated = now(), + component = ?, status = ?::issue_status, category = ?::issue_category, subject = ?, + description = ?, assignee = ?, eta = ? + where issueid = ? + """ + ) + } + private val stmtInsertAffectedVersion by lazy { + connection.prepareStatement( + "insert into lpit_issue_affected_version (issueid, versionid) values (?,?)" + ) + } + private val stmtInsertResolvedVersion by lazy { + connection.prepareStatement( + "insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)" + ) + } + private val stmtClearAffectedVersions by lazy { + connection.prepareStatement("delete from lpit_issue_affected_version where issueid = ?") + } + private val stmtClearResolvedVersions by lazy { + connection.prepareStatement("delete from lpit_issue_resolved_version where issueid = ?") + } + + override fun findIssue(id: Int): Issue? { + stmtFindIssueByID.setInt(1, id) + return selectIssues(stmtFindIssueByID).firstOrNull() + } + + private fun insertVersionInfo(issue: Issue) { + stmtInsertAffectedVersion.setInt(1, issue.id) + stmtInsertResolvedVersion.setInt(1, issue.id) + issue.affectedVersions.forEach { + stmtInsertAffectedVersion.setInt(2, it.id) + stmtInsertAffectedVersion.execute() + } + issue.resolvedVersions.forEach { + stmtInsertResolvedVersion.setInt(2, it.id) + stmtInsertResolvedVersion.execute() + } + } + + override fun insertIssue(issue: Issue) { + val col = setIssueFields(stmtInsertIssue, issue) + stmtInsertIssue.setInt(col, issue.project.id) + stmtInsertIssue.executeQuery().use { rs -> + rs.next() + issue.id = rs.getInt(1) + } + insertVersionInfo(issue) + } + + override fun updateIssue(issue: Issue) { + val col = setIssueFields(stmtUpdateIssue, issue) + stmtUpdateIssue.setInt(col, issue.id) + // TODO: improve by only inserting / deleting changed version information + stmtClearAffectedVersions.setInt(1, issue.id) + stmtClearResolvedVersions.setInt(1, issue.id) + stmtClearAffectedVersions.execute() + stmtClearResolvedVersions.execute() + insertVersionInfo(issue) + } + + //</editor-fold> + + //<editor-fold desc="IssueComment"> + + private fun selectComments(stmt: PreparedStatement) = sequence { + stmt.executeQuery().use { rs -> + while (rs.next()) { + yield(IssueComment(rs.getInt("commentid"), rs.getInt("issueid")).apply { + created = rs.getTimestamp("created") + updated = rs.getTimestamp("updated") + updateCount = rs.getInt("updatecount") + comment = rs.getString("comment") + author = selectUserInfo(rs) + }) + } + } + } + + private val stmtComments by lazy { + connection.prepareStatement( + "select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created" + ) + } + private val stmtInsertComment by lazy { + connection.prepareStatement( + "insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)" + ) + } + private val stmtUpdateIssueDate by lazy { + connection.prepareStatement( + "update lpit_issue set updated = now() where issueid = ?" + ) + } + + override fun listComments(issue: Issue): List<IssueComment> { + stmtComments.setInt(1, issue.id) + return selectComments(stmtComments).toList() + } + + override fun insertComment(issueComment: IssueComment) { + with(issueComment) { + stmtUpdateIssueDate.setInt(1, issueid) + stmtInsertComment.setInt(1, issueid) + stmtInsertComment.setStringSafe(2, comment) + stmtInsertComment.setIntOrNull(3, author?.id) + } + stmtInsertComment.execute() + stmtUpdateIssueDate.execute() + } + //</editor-fold> +} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGComponentDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.AbstractComponentDao -import de.uapcore.lightpit.dao.Functions -import de.uapcore.lightpit.entities.Component -import de.uapcore.lightpit.entities.Project -import de.uapcore.lightpit.entities.User -import de.uapcore.lightpit.types.WebColor -import java.sql.Connection -import java.sql.PreparedStatement -import java.sql.ResultSet - -class PGComponentDao(connection: Connection) : AbstractComponentDao() { - - private val query = "select id, name, node, color, ordinal, description, " + - "userid, username, givenname, lastname, mail " + - "from lpit_component " + - "left join lpit_user on lead = userid" - - private val listStmt = connection.prepareStatement("$query where project = ? order by ordinal, lower(name)") - private val findStmt = connection.prepareStatement("$query where id = ? ") - private val findByNodeStmt = connection.prepareStatement("$query where project = ? and node = ?") - private val insertStmt = connection.prepareStatement( - "insert into lpit_component (name, node, color, ordinal, description, lead, project) values (?, ?, ?, ?, ?, ?, ?)" - ) - private val updateStmt = connection.prepareStatement( - "update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?" - ) - - override fun mapResult(rs: ResultSet): Component { - val component = Component(rs.getInt("id")) - component.name = rs.getString("name") - component.node = rs.getString("node") - component.color = try { - WebColor(rs.getString("color")) - } catch (ex: IllegalArgumentException) { - WebColor("000000") - } - component.ordinal = rs.getInt("ordinal") - component.description = rs.getString("description") - component.lead = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() } - return component - } - - private fun setColumns(stmt: PreparedStatement, instance: Component): Int { - var column = 0 - stmt.setString(++column, instance.name) - stmt.setString(++column, instance.node) - stmt.setString(++column, instance.color.hex) - stmt.setInt(++column, instance.ordinal) - Functions.setStringOrNull(stmt, ++column, instance.description) - setForeignKeyOrNull(stmt, ++column, instance.lead, User::id) - return column - } - - override fun save(instance: Component, parent: Project) { - var column = setColumns(insertStmt, instance) - insertStmt.setInt(++column, parent.id) - insertStmt.executeUpdate() - } - - override fun update(instance: Component): Boolean { - var column = setColumns(updateStmt, instance) - updateStmt.setInt(++column, instance.id) - return updateStmt.executeUpdate() > 0 - } - - - override fun list(parent: Project): List<Component> { - listStmt.setInt(1, parent.id) - return super.list(listStmt) - } - - override fun find(id: Int): Component? { - findStmt.setInt(1, id) - return super.find(findStmt) - } - - override fun findByNode(parent: Project, node: String): Component? { - findByNodeStmt.setInt(1, parent.id) - findByNodeStmt.setString(2, node) - return super.find(findByNodeStmt) - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGDaoProvider.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.DaoProvider -import java.sql.Connection - -class PGDaoProvider(connection: Connection) : DaoProvider { - override val userDao = PGUserDao(connection) - override val projectDao = PGProjectDao(connection) - override val componentDao = PGComponentDao(connection) - override val versionDao = PGVersionDao(connection) - override val issueDao = PGIssueDao(connection) -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGIssueDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.AbstractIssueDao -import de.uapcore.lightpit.dao.Functions -import de.uapcore.lightpit.entities.* -import java.sql.Connection -import java.sql.PreparedStatement -import java.sql.ResultSet -import java.sql.Types - -class PGIssueDao(connection: Connection) : AbstractIssueDao() { - - private val query = "select issueid, i.project, p.name as projectname, p.node as projectnode, " + - "component, c.name as componentname, c.node as componentnode, " + - "status, category, subject, i.description, " + - "userid, username, givenname, lastname, mail, " + - "created, updated, eta " + - "from lpit_issue i " + - "join lpit_project p on i.project = projectid " + - "left join lpit_component c on component = c.id " + - "left join lpit_user on userid = assignee " - private val list = connection.prepareStatement(query + - "where i.project = ? and coalesce(component, -1) = coalesce(?, component, -1)") - private val listForVersion = connection.prepareStatement( - "with issue_version as ( " + - "select issueid, versionid from lpit_issue_affected_version union " + - "select issueid, versionid from lpit_issue_resolved_version) " + - query + - "left join issue_version using (issueid) " + - "where i.project = ? " + - "and coalesce(versionid,-1) = ? and coalesce(component, -1) = coalesce(?, component, -1)" - ) - private val find = connection.prepareStatement(query + "where issueid = ? ") - private val insert = connection.prepareStatement( - "insert into lpit_issue (project, component, status, category, subject, description, assignee, eta) " + - "values (?, ?, ?::issue_status, ?::issue_category, ?, ?, ?, ?) returning issueid" - ) - private val update = connection.prepareStatement( - "update lpit_issue set " + - "updated = now(), component = ?, status = ?::issue_status, category = ?::issue_category, " + - "subject = ?, description = ?, assignee = ?, eta = ? where issueid = ?" - ) - private val affectedVersions = connection.prepareStatement( - "select versionid, name, status, ordinal, node " + - "from lpit_version join lpit_issue_affected_version using (versionid) " + - "where issueid = ? " + - "order by ordinal, name" - ) - private val clearAffected = connection.prepareStatement("delete from lpit_issue_affected_version where issueid = ?") - private val insertAffected = connection.prepareStatement("insert into lpit_issue_affected_version (issueid, versionid) values (?,?)") - - private val resolvedVersions = connection.prepareStatement( - "select versionid, name, status, ordinal, node " + - "from lpit_version v join lpit_issue_resolved_version using (versionid) " + - "where issueid = ? " + - "order by ordinal, name" - ) - private val clearResolved = connection.prepareStatement("delete from lpit_issue_resolved_version where issueid = ?") - private val insertResolved = connection.prepareStatement("insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)") - private val insertComment = connection.prepareStatement( - "insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)" - ) - private val updateComment = connection.prepareStatement( - "update lpit_issue_comment set comment = ?, updated = now(), updatecount = updatecount+1 where commentid = ?" - ) - private val listComments = connection.prepareStatement( - "select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created" - ) - - private val updateIssueLastModified = connection.prepareStatement( - "update lpit_issue set updated = now() where issueid = ?" - ); - - override fun mapResult(rs: ResultSet): Issue { - val project = Project(rs.getInt("project")) - project.name = rs.getString("projectname") - project.node = rs.getString("projectnode") - val issue = Issue(rs.getInt("issueid")) - issue.project = project - issue.component = rs.getInt("component").let { id -> - if (rs.wasNull()) { - null - } else { - val component = Component(id) - component.name = rs.getString("componentname") - component.node = rs.getString("componentnode") - component - } - } - issue.status = IssueStatus.valueOf(rs.getString("status")) - issue.category = IssueCategory.valueOf(rs.getString("category")) - issue.subject = rs.getString("subject") - issue.description = rs.getString("description") - issue.assignee = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() } - issue.created = rs.getTimestamp("created") - issue.updated = rs.getTimestamp("updated") - issue.eta = rs.getDate("eta") - return issue - } - - private fun updateVersionLists(instance: Issue) { - clearAffected.setInt(1, instance.id) - clearResolved.setInt(1, instance.id) - insertAffected.setInt(1, instance.id) - insertResolved.setInt(1, instance.id) - clearAffected.executeUpdate() - clearResolved.executeUpdate() - for (v: Version in instance.affectedVersions) { - insertAffected.setInt(2, v.id) - insertAffected.executeUpdate() - } - for (v: Version in instance.resolvedVersions) { - insertResolved.setInt(2, v.id) - insertResolved.executeUpdate() - } - } - - private fun setData(stmt: PreparedStatement, column: Int, instance: Issue): Int { - var col = column - setForeignKeyOrNull(stmt, ++col, instance.component, Component::id) - stmt.setString(++col, instance.status.name) - stmt.setString(++col, instance.category.name) - stmt.setString(++col, instance.subject) - Functions.setStringOrNull(stmt, ++col, instance.description) - setForeignKeyOrNull(stmt, ++col, instance.assignee, User::id) - Functions.setDateOrNull(stmt, ++col, instance.eta) - return col - } - - override fun save(instance: Issue, parent: Project) { - instance.project = parent - var column = 0 - insert.setInt(++column, parent.id) - setData(insert, column, instance) - // insert and retrieve the ID - val rs = insert.executeQuery() - rs.next() - instance.id = rs.getInt(1) - updateVersionLists(instance) - } - - override fun update(instance: Issue): Boolean { - var column = setData(update, 0, instance) - update.setInt(++column, instance.id) - return if (update.executeUpdate() > 0) { - updateVersionLists(instance) - true - } else { - false - } - } - - override fun list(parent: Project): List<Issue> { - list.setInt(1, parent.id) - list.setNull(2, Types.INTEGER) - return super.list(list) - } - - override fun list(project: Project, component: Component?, version: Version?): List<Issue> { - listForVersion.setInt(1, project.id) - listForVersion.setInt(2, version?.id ?: -1) - listForVersion.setInt(3, component?.id ?: -1) - return super.list(listForVersion) - } - - override fun list(project: Project, version: Version?): List<Issue> { - listForVersion.setInt(1, project.id) - listForVersion.setInt(2, version?.id ?: -1) - listForVersion.setNull(3, Types.INTEGER) - return super.list(listForVersion) - } - - override fun list(project: Project, component: Component?): List<Issue> { - list.setInt(1, project.id) - list.setInt(2, component?.id ?: -1) - return super.list(list) - } - - override fun find(id: Int): Issue? { - find.setInt(1, id) - return super.find(find) - } - - private fun listVersions(stmt: PreparedStatement, issue: Issue): List<Version> { - stmt.setInt(1, issue.id) - return sequence { - stmt.executeQuery().use { result -> - while (result.next()) yield(PGVersionDao.mapResult(result)) - } - }.toList() - } - - override fun joinVersionInformation(issue: Issue) { - issue.affectedVersions = listVersions(affectedVersions, issue) - issue.resolvedVersions = listVersions(resolvedVersions, issue) - } - - override fun listComments(issue: Issue): List<IssueComment> { - listComments.setInt(1, issue.id) - return sequence { - listComments.executeQuery().use { rs -> - while (rs.next()) { - val comment = IssueComment(rs.getInt("commentid")) - comment.created = rs.getTimestamp("created") - comment.updated = rs.getTimestamp("updated") - comment.updateCount = rs.getInt("updatecount") - comment.comment = rs.getString("comment") - comment.author = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() } - yield(comment) - } - } - }.toList() - } - - override fun saveComment(issue: Issue, comment: IssueComment) { - if (comment.id >= 0) { - updateComment.setString(1, comment.comment) - updateComment.setInt(2, comment.id) - updateComment.execute() - } else { - insertComment.setInt(1, issue.id) - insertComment.setString(2, comment.comment) - setForeignKeyOrNull(insertComment, 3, comment.author, User::id) - insertComment.execute() - } - updateIssueLastModified.setInt(1, issue.id); - updateIssueLastModified.execute(); - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGProjectDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.AbstractProjectDao -import de.uapcore.lightpit.dao.Functions -import de.uapcore.lightpit.entities.IssueSummary -import de.uapcore.lightpit.entities.Project -import de.uapcore.lightpit.entities.User -import java.sql.Connection -import java.sql.PreparedStatement -import java.sql.ResultSet - -class PGProjectDao(connection: Connection) : AbstractProjectDao() { - - private val query = "select projectid, name, node, description, repourl, " + - "userid, username, lastname, givenname, mail " + - "from lpit_project " + - "left join lpit_user owner on lpit_project.owner = owner.userid " - - private val listStmt = connection.prepareStatement("$query order by name") - private val findStmt = connection.prepareStatement("$query where projectid = ?") - private val findByNodeStmt = connection.prepareStatement("$query where node = ?") - private val issueSummaryStmt = connection.prepareStatement( - "select phase, count(*) as total " + - "from lpit_issue " + - "join lpit_issue_phases using(status) " + - "where project = ? " + - "group by phase " - ) - private val insertStmt = connection.prepareStatement( - "insert into lpit_project (name, node, description, repourl, owner) values (?, ?, ?, ?, ?)" - ) - private val updateStmt = connection.prepareStatement( - "update lpit_project set name = ?, node = ?, description = ?, repourl = ?, owner = ? where projectid = ?" - ) - - override fun mapResult(rs: ResultSet): Project { - val proj = Project(rs.getInt("projectid")) - proj.name = rs.getString("name") - proj.node = rs.getString("node") - proj.description = rs.getString("description") - proj.repoUrl = rs.getString("repourl") - proj.owner = PGUserDao.mapResult(rs).takeUnless { rs.wasNull() } - return proj - } - - override fun getIssueSummary(project: Project): IssueSummary { - issueSummaryStmt.setInt(1, project.id) - val result = issueSummaryStmt.executeQuery() - val summary = IssueSummary() - while (result.next()) { - val phase = result.getInt("phase") - val total = result.getInt("total") - when (phase) { - 0 -> summary.open = total - 1 -> summary.active = total - 2 -> summary.done = total - } - } - return summary - } - - private fun setColumns(stmt: PreparedStatement, instance: Project): Int { - var column = 0 - stmt.setString(++column, instance.name) - stmt.setString(++column, instance.node) - Functions.setStringOrNull(stmt, ++column, instance.description) - Functions.setStringOrNull(stmt, ++column, instance.repoUrl) - setForeignKeyOrNull(stmt, ++column, instance.owner, User::id) - return column - } - - override fun save(instance: Project) { - setColumns(insertStmt, instance) - insertStmt.executeUpdate() - } - - override fun update(instance: Project): Boolean { - var column = setColumns(updateStmt, instance) - updateStmt.setInt(++column, instance.id) - return updateStmt.executeUpdate() > 0 - } - - override fun list(): List<Project> { - return super.list(listStmt) - } - - override fun find(id: Int): Project? { - findStmt.setInt(1, id) - return super.find(findStmt) - } - - override fun findByNode(node: String): Project? { - findByNodeStmt.setString(1, node) - return super.find(findByNodeStmt) - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGUserDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.AbstractUserDao -import de.uapcore.lightpit.dao.Functions -import de.uapcore.lightpit.entities.User -import java.sql.Connection -import java.sql.ResultSet - -class PGUserDao(connection: Connection) : AbstractUserDao() { - - companion object { - fun mapResult(rs: ResultSet): User { - val id = rs.getInt("userid") - return if (rs.wasNull()) { - User(-1) - } else { - val user = User(id) - user.username = rs.getString("username") - user.givenname = Functions.getSafeString(rs, "givenname") - user.lastname = Functions.getSafeString(rs, "lastname") - user.mail = Functions.getSafeString(rs, "mail") - user - } - } - } - - private val listStmt = connection.prepareStatement( - "select userid, username, lastname, givenname, mail " + - "from lpit_user where userid >= 0 " + - "order by username") - private val findStmt = connection.prepareStatement( - "select userid, username, lastname, givenname, mail " + - "from lpit_user where userid = ? ") - private val findByUsernameStmt = connection.prepareStatement( - "select userid, username, lastname, givenname, mail " + - "from lpit_user where lower(username) = lower(?) ") - private val insertStmt = connection.prepareStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)") - private val updateStmt = connection.prepareStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?") - - override fun mapResult(rs: ResultSet): User = Companion.mapResult(rs) - - override fun save(instance: User) { - insertStmt.setString(1, instance.username) - Functions.setStringOrNull(insertStmt, 2, instance.lastname) - Functions.setStringOrNull(insertStmt, 3, instance.givenname) - Functions.setStringOrNull(insertStmt, 4, instance.mail) - insertStmt.executeUpdate() - } - - override fun update(instance: User): Boolean { - Functions.setStringOrNull(updateStmt, 1, instance.lastname) - Functions.setStringOrNull(updateStmt, 2, instance.givenname) - Functions.setStringOrNull(updateStmt, 3, instance.mail) - updateStmt.setInt(4, instance.id) - return updateStmt.executeUpdate() > 0 - } - - override fun list(): List<User> = super.list(listStmt) - - override fun find(id: Int): User? { - findStmt.setInt(1, id) - return super.find(findStmt) - } - - override fun findByUsername(username: String): User? { - findByUsernameStmt.setString(1, username) - return super.find(findByUsernameStmt) - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/dao/postgres/PGVersionDao.kt Sun Dec 20 11:06:25 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/* - * Copyright 2020 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.lightpit.dao.postgres - -import de.uapcore.lightpit.dao.AbstractVersionDao -import de.uapcore.lightpit.entities.Project -import de.uapcore.lightpit.entities.Version -import de.uapcore.lightpit.entities.VersionStatus -import java.sql.Connection -import java.sql.PreparedStatement -import java.sql.ResultSet - -class PGVersionDao(connection: Connection) : AbstractVersionDao() { - - companion object { - fun mapResult(rs: ResultSet): Version { - val id = rs.getInt("versionid") - return if (rs.wasNull()) { - Version(-1) - } else { - val version = Version(id) - version.name = rs.getString("name") - version.node = rs.getString("node") - version.ordinal = rs.getInt("ordinal") - version.status = VersionStatus.valueOf(rs.getString("status")) - version - } - } - } - - private val query = "select versionid, project, name, node, ordinal, status from lpit_version" - private val listStmt = connection.prepareStatement(query + " where project = ? " + - "order by ordinal desc, lower(name) desc") - private val findStmt = connection.prepareStatement("$query where versionid = ?") - private val findByNodeStmt = connection.prepareStatement("$query where project = ? and node = ?") - private val insertStmt = connection.prepareStatement( - "insert into lpit_version (name, node, ordinal, status, project) values (?, ?, ?, ?::version_status, ?)" - ) - private val updateStmt = connection.prepareStatement( - "update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status where versionid = ?" - ) - - override fun mapResult(rs: ResultSet): Version = Companion.mapResult(rs) - - private fun setFields(stmt: PreparedStatement, instance: Version): Int { - var column = 0 - stmt.setString(++column, instance.name) - stmt.setString(++column, instance.node) - stmt.setInt(++column, instance.ordinal) - stmt.setString(++column, instance.status.name) - return column - } - - override fun save(instance: Version, parent: Project) { - var column = setFields(insertStmt, instance) - insertStmt.setInt(++column, parent.id) - insertStmt.executeUpdate() - } - - override fun update(instance: Version): Boolean { - var column = setFields(updateStmt, instance) - updateStmt.setInt(++column, instance.id) - return updateStmt.executeUpdate() > 0 - } - - override fun list(parent: Project): List<Version> { - listStmt.setInt(1, parent.id) - return super.list(listStmt) - } - - override fun find(id: Int): Version? { - findStmt.setInt(1, id) - return super.find(findStmt) - } - - override fun findByNode(parent: Project, node: String): Version? { - findByNodeStmt.setInt(1, parent.id) - findByNodeStmt.setString(2, node) - return super.find(findByNodeStmt) - } -} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt Mon Dec 21 18:29:34 2020 +0100 @@ -27,7 +27,7 @@ import de.uapcore.lightpit.types.WebColor -data class Component(val id: Int) { +data class Component(override val id: Int, var projectid: Int) : Entity { var name: String = "" var node: String = name var color = WebColor("000000")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Entity.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.entities + +interface Entity { + val id: Int +} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt Mon Dec 21 18:29:34 2020 +0100 @@ -25,81 +25,32 @@ package de.uapcore.lightpit.entities +import de.uapcore.lightpit.types.IssueCategory +import de.uapcore.lightpit.types.IssueStatus +import de.uapcore.lightpit.types.IssueStatusPhase import java.sql.Date import java.sql.Timestamp import java.time.Instant -import kotlin.math.roundToInt -data class IssueStatusPhase(val number: Int) { - companion object { - val Open = IssueStatusPhase(0) - val WorkInProgress = IssueStatusPhase(1) - val Done = IssueStatusPhase(2) - } -} - -enum class IssueStatus(val phase: IssueStatusPhase) { - InSpecification(IssueStatusPhase.Open), - ToDo(IssueStatusPhase.Open), - Scheduled(IssueStatusPhase.Open), - InProgress(IssueStatusPhase.WorkInProgress), - InReview(IssueStatusPhase.WorkInProgress), - Done(IssueStatusPhase.Done), - Rejected(IssueStatusPhase.Done), - Withdrawn(IssueStatusPhase.Done), - Duplicate(IssueStatusPhase.Done); -} - -enum class IssueCategory { - Feature, Improvement, Bug, Task, Test -} - -data class Issue(var id: Int) { - - var project: Project? = null - var component: Component? = null +data class Issue(override var id: Int, var project: Project, var component: Component? = null) : Entity { var status = IssueStatus.InSpecification var category = IssueCategory.Feature - var subject: String? = null + var subject: String = "" var description: String? = null var assignee: User? = null - var affectedVersions = emptyList<Version>() - var resolvedVersions = emptyList<Version>() - var created: Timestamp = Timestamp.from(Instant.now()) var updated: Timestamp = Timestamp.from(Instant.now()) var eta: Date? = null + var affectedVersions = emptyList<Version>() + var resolvedVersions = emptyList<Version>() + /** * An issue is overdue, if it is not done and the ETA is before the current time. */ val overdue get() = status.phase != IssueStatusPhase.Done && eta?.before(Date(System.currentTimeMillis())) ?: false } -class IssueSummary { - var open = 0 - var active = 0 - var done = 0 - - val total get() = open + active + done - - val openPercent get() = 100 - activePercent - donePercent - val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0 - val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100 - - /** - * Adds the specified issue to the summary by incrementing the respective counter. - * @param issue the issue - */ - fun add(issue: Issue) { - when (issue.status.phase) { - IssueStatusPhase.Open -> open++ - IssueStatusPhase.WorkInProgress -> active++ - IssueStatusPhase.Done -> done++ - } - } -} -
--- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt Mon Dec 21 18:29:34 2020 +0100 @@ -28,9 +28,9 @@ import java.sql.Timestamp import java.time.Instant -data class IssueComment(val id: Int) { +data class IssueComment(override val id: Int, val issueid: Int) : Entity { var author: User? = null - var comment: String? = null + var comment: String = "" var created: Timestamp = Timestamp.from(Instant.now()) var updated: Timestamp = Timestamp.from(Instant.now()) var updateCount = 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueSummary.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.entities + +import de.uapcore.lightpit.types.IssueStatusPhase +import kotlin.math.roundToInt + +class IssueSummary { + var open = 0 + var active = 0 + var done = 0 + + val total get() = open + active + done + + val openPercent get() = 100 - activePercent - donePercent + val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0 + val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100 + + /** + * Adds the specified issue to the summary by incrementing the respective counter. + * @param issue the issue + */ + fun add(issue: Issue) { + when (issue.status.phase) { + IssueStatusPhase.Open -> open++ + IssueStatusPhase.WorkInProgress -> active++ + IssueStatusPhase.Done -> done++ + } + } +} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Mon Dec 21 18:29:34 2020 +0100 @@ -25,9 +25,9 @@ package de.uapcore.lightpit.entities -data class Project(val id: Int) { - var name: String? = null - var node: String? = null +data class Project(override val id: Int) : Entity { + var name: String = "" + var node: String = name var description: String? = null var repoUrl: String? = null var owner: User? = null
--- a/src/main/kotlin/de/uapcore/lightpit/entities/User.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/User.kt Mon Dec 21 18:29:34 2020 +0100 @@ -25,16 +25,17 @@ package de.uapcore.lightpit.entities -data class User(val id: Int) { - var username = "" - var mail = "" - var givenname = "" - var lastname = "" +data class User(override val id: Int) : Entity { + var username: String = "" + var mail: String? = null + var givenname: String? = null + var lastname: String? = null - val shortDisplayname: String get() { - val str = "$givenname $lastname" - return if (str.isBlank()) username else str.trim() - } + val shortDisplayname: String + get() { + val str = "${givenname ?: ""} ${lastname ?: ""}" + return if (str.isBlank()) username else str.trim() + } - val displayname: String get() = if (mail.isBlank()) shortDisplayname else "$shortDisplayname <$mail>" + val displayname: String get() = if (mail.isNullOrBlank()) shortDisplayname else "$shortDisplayname <$mail>" } \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Mon Dec 21 18:29:34 2020 +0100 @@ -25,12 +25,9 @@ package de.uapcore.lightpit.entities -enum class VersionStatus { - Future, Unreleased, Released, LTS, Deprecated; - val isReleased get() = this.ordinal >= Released.ordinal -} +import de.uapcore.lightpit.types.VersionStatus -data class Version(val id: Int) : Comparable<Version> { +data class Version(override val id: Int, var projectid: Int) : Entity, Comparable<Version> { var name: String = "" var node = name var ordinal = 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/filter/Filter.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.filter + +sealed class Filter<T> +class AllFilter<T> : Filter<T>() +class NoneFilter<T> : Filter<T>() +data class SpecificFilter<T>(val obj: T) : Filter<T>() +data class RangeFilter<T>(val lower: T, val upper: T) : Filter<T>() where T : Comparable<T>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/filter/IssueFilter.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.filter + +import de.uapcore.lightpit.entities.Component +import de.uapcore.lightpit.entities.Project +import de.uapcore.lightpit.entities.Version + +data class IssueFilter( + val project: Filter<Project> = AllFilter(), + val version: Filter<Version> = AllFilter(), + val component: Filter<Component> = AllFilter() +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/types/IssueCategory.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.types + +enum class IssueCategory { + Feature, Improvement, Bug, Task, Test +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/types/IssueStatus.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.types + +enum class IssueStatus(val phase: IssueStatusPhase) { + InSpecification(IssueStatusPhase.Open), + ToDo(IssueStatusPhase.Open), + Scheduled(IssueStatusPhase.Open), + InProgress(IssueStatusPhase.WorkInProgress), + InReview(IssueStatusPhase.WorkInProgress), + Done(IssueStatusPhase.Done), + Rejected(IssueStatusPhase.Done), + Withdrawn(IssueStatusPhase.Done), + Duplicate(IssueStatusPhase.Done); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/types/IssueStatusPhase.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.types + +data class IssueStatusPhase(val number: Int) { + companion object { + val Open = IssueStatusPhase(0) + val WorkInProgress = IssueStatusPhase(1) + val Done = IssueStatusPhase(2) + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/types/VersionStatus.kt Mon Dec 21 18:29:34 2020 +0100 @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.uapcore.lightpit.types + +enum class VersionStatus { + Future, Unreleased, Released, LTS, Deprecated; + + val isReleased get() = this.ordinal >= Released.ordinal +} \ No newline at end of file
--- a/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Sun Dec 20 11:06:25 2020 +0100 +++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Mon Dec 21 18:29:34 2020 +0100 @@ -26,7 +26,7 @@ --%> <%@page pageEncoding="UTF-8" import="de.uapcore.lightpit.viewmodel.ProjectView" - import="de.uapcore.lightpit.entities.VersionStatus" + import="de.uapcore.lightpit.types.VersionStatus" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>