adds version management

Sun, 17 May 2020 16:23:39 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 May 2020 16:23:39 +0200
changeset 59
c759c60507a2
parent 58
8d3047f78190
child 60
ed51c5b1f3e5

adds version management

src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/VersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Version.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/UsersModule.java file | annotate | diff | comparison | revisions
src/main/resources/localization/projects.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/users.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/version-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/versions.jsp file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
     1.1 --- a/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Sun May 17 16:00:13 2020 +0200
     1.2 +++ b/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Sun May 17 16:23:39 2020 +0200
     1.3 @@ -30,6 +30,6 @@
     1.4  
     1.5  public interface DataAccessObjects {
     1.6      UserDao getUserDao();
     1.7 -
     1.8      ProjectDao getProjectDao();
     1.9 +    VersionDao getVersionDao();
    1.10  }
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Sun May 17 16:23:39 2020 +0200
     2.3 @@ -0,0 +1,53 @@
     2.4 +/*
     2.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.6 + *
     2.7 + * Copyright 2018 Mike Becker. All rights reserved.
     2.8 + *
     2.9 + * Redistribution and use in source and binary forms, with or without
    2.10 + * modification, are permitted provided that the following conditions are met:
    2.11 + *
    2.12 + *   1. Redistributions of source code must retain the above copyright
    2.13 + *      notice, this list of conditions and the following disclaimer.
    2.14 + *
    2.15 + *   2. Redistributions in binary form must reproduce the above copyright
    2.16 + *      notice, this list of conditions and the following disclaimer in the
    2.17 + *      documentation and/or other materials provided with the distribution.
    2.18 + *
    2.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    2.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    2.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    2.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    2.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    2.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    2.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    2.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    2.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    2.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    2.29 + * POSSIBILITY OF SUCH DAMAGE.
    2.30 + *
    2.31 + */
    2.32 +package de.uapcore.lightpit.dao;
    2.33 +
    2.34 +import de.uapcore.lightpit.entities.Project;
    2.35 +import de.uapcore.lightpit.entities.Version;
    2.36 +
    2.37 +import java.sql.SQLException;
    2.38 +import java.util.List;
    2.39 +
    2.40 +public interface VersionDao {
    2.41 +
    2.42 +    Version find(int id) throws SQLException;
    2.43 +    void save(Version instance) throws SQLException;
    2.44 +    boolean update(Version instance) throws SQLException;
    2.45 +    default void saveOrUpdate(Version instance) throws SQLException {
    2.46 +        if (!update(instance)) save(instance);
    2.47 +    }
    2.48 +
    2.49 +    /**
    2.50 +     * Lists all versions for the specified project.
    2.51 +     * @param project the project
    2.52 +     * @return a list of versions
    2.53 +     * @throws SQLException on any kind of SQL error
    2.54 +     */
    2.55 +    List<Version> list(Project project) throws SQLException;
    2.56 +}
     3.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Sun May 17 16:00:13 2020 +0200
     3.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Sun May 17 16:23:39 2020 +0200
     3.3 @@ -31,6 +31,7 @@
     3.4  import de.uapcore.lightpit.dao.DataAccessObjects;
     3.5  import de.uapcore.lightpit.dao.ProjectDao;
     3.6  import de.uapcore.lightpit.dao.UserDao;
     3.7 +import de.uapcore.lightpit.dao.VersionDao;
     3.8  
     3.9  import java.sql.Connection;
    3.10  import java.sql.SQLException;
    3.11 @@ -39,10 +40,12 @@
    3.12  
    3.13      private final UserDao userDao;
    3.14      private final ProjectDao projectDao;
    3.15 +    private final VersionDao versionDao;
    3.16  
    3.17      public PGDataAccessObjects(Connection connection) throws SQLException {
    3.18          userDao = new PGUserDao(connection);
    3.19          projectDao = new PGProjectDao(connection);
    3.20 +        versionDao = new PGVersionDao(connection);
    3.21      }
    3.22  
    3.23      @Override
    3.24 @@ -54,4 +57,9 @@
    3.25      public ProjectDao getProjectDao() {
    3.26          return projectDao;
    3.27      }
    3.28 +
    3.29 +    @Override
    3.30 +    public VersionDao getVersionDao() {
    3.31 +        return versionDao;
    3.32 +    }
    3.33  }
     4.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Sun May 17 16:00:13 2020 +0200
     4.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Sun May 17 16:23:39 2020 +0200
     4.3 @@ -28,7 +28,6 @@
     4.4   */
     4.5  package de.uapcore.lightpit.dao.postgres;
     4.6  
     4.7 -import de.uapcore.lightpit.dao.GenericDao;
     4.8  import de.uapcore.lightpit.dao.ProjectDao;
     4.9  import de.uapcore.lightpit.entities.Project;
    4.10  import de.uapcore.lightpit.entities.User;
    4.11 @@ -44,7 +43,7 @@
    4.12  import static de.uapcore.lightpit.dao.Functions.setForeignKeyOrNull;
    4.13  import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
    4.14  
    4.15 -public final class PGProjectDao implements ProjectDao, GenericDao<Project> {
    4.16 +public final class PGProjectDao implements ProjectDao {
    4.17  
    4.18      private final PreparedStatement insert, update, list, find;
    4.19  
     5.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Sun May 17 16:00:13 2020 +0200
     5.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Sun May 17 16:23:39 2020 +0200
     5.3 @@ -28,7 +28,6 @@
     5.4   */
     5.5  package de.uapcore.lightpit.dao.postgres;
     5.6  
     5.7 -import de.uapcore.lightpit.dao.GenericDao;
     5.8  import de.uapcore.lightpit.dao.UserDao;
     5.9  import de.uapcore.lightpit.entities.User;
    5.10  
    5.11 @@ -42,7 +41,7 @@
    5.12  
    5.13  import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
    5.14  
    5.15 -public final class PGUserDao implements UserDao, GenericDao<User> {
    5.16 +public final class PGUserDao implements UserDao {
    5.17  
    5.18      private final PreparedStatement insert, update, list, find;
    5.19  
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Sun May 17 16:23:39 2020 +0200
     6.3 @@ -0,0 +1,120 @@
     6.4 +/*
     6.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 + *
     6.7 + * Copyright 2018 Mike Becker. All rights reserved.
     6.8 + *
     6.9 + * Redistribution and use in source and binary forms, with or without
    6.10 + * modification, are permitted provided that the following conditions are met:
    6.11 + *
    6.12 + *   1. Redistributions of source code must retain the above copyright
    6.13 + *      notice, this list of conditions and the following disclaimer.
    6.14 + *
    6.15 + *   2. Redistributions in binary form must reproduce the above copyright
    6.16 + *      notice, this list of conditions and the following disclaimer in the
    6.17 + *      documentation and/or other materials provided with the distribution.
    6.18 + *
    6.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    6.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    6.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    6.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    6.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    6.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    6.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    6.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    6.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    6.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    6.29 + * POSSIBILITY OF SUCH DAMAGE.
    6.30 + *
    6.31 + */
    6.32 +package de.uapcore.lightpit.dao.postgres;
    6.33 +
    6.34 +import de.uapcore.lightpit.dao.VersionDao;
    6.35 +import de.uapcore.lightpit.entities.Project;
    6.36 +import de.uapcore.lightpit.entities.Version;
    6.37 +import de.uapcore.lightpit.entities.VersionStatus;
    6.38 +
    6.39 +import java.sql.Connection;
    6.40 +import java.sql.PreparedStatement;
    6.41 +import java.sql.ResultSet;
    6.42 +import java.sql.SQLException;
    6.43 +import java.util.ArrayList;
    6.44 +import java.util.List;
    6.45 +import java.util.Objects;
    6.46 +
    6.47 +public final class PGVersionDao implements VersionDao {
    6.48 +
    6.49 +    private final PreparedStatement insert, update, list, find;
    6.50 +
    6.51 +    public PGVersionDao(Connection connection) throws SQLException {
    6.52 +        list = connection.prepareStatement(
    6.53 +                "select id, project, name, ordinal, status " +
    6.54 +                        "from lpit_version " +
    6.55 +                        "where project = ? " +
    6.56 +                        "order by ordinal, name");
    6.57 +
    6.58 +        find = connection.prepareStatement(
    6.59 +                "select id, project, name, ordinal, status " +
    6.60 +                        "from lpit_version " +
    6.61 +                        "where id = ?");
    6.62 +
    6.63 +        insert = connection.prepareStatement(
    6.64 +                "insert into lpit_version (project, name, ordinal, status) values (?, ?, ?, ?::version_status)"
    6.65 +        );
    6.66 +        update = connection.prepareStatement(
    6.67 +                "update lpit_version set name = ?, ordinal = ?, status = ?::version_status where id = ?"
    6.68 +        );
    6.69 +    }
    6.70 +
    6.71 +    public Version mapColumns(ResultSet result) throws SQLException {
    6.72 +        final var version = new Version(result.getInt("id"), new Project(result.getInt("project")));
    6.73 +        version.setName(result.getString("name"));
    6.74 +        version.setOrdinal(result.getInt("ordinal"));
    6.75 +        version.setStatus(VersionStatus.valueOf(result.getString("status")));
    6.76 +        return version;
    6.77 +    }
    6.78 +
    6.79 +    @Override
    6.80 +    public void save(Version instance) throws SQLException {
    6.81 +        Objects.requireNonNull(instance.getName());
    6.82 +        Objects.requireNonNull(instance.getProject());
    6.83 +        insert.setInt(1, instance.getProject().getId());
    6.84 +        insert.setString(2, instance.getName());
    6.85 +        insert.setInt(3, instance.getOrdinal());
    6.86 +        insert.setString(4, instance.getStatus().name());
    6.87 +        insert.executeUpdate();
    6.88 +    }
    6.89 +
    6.90 +    @Override
    6.91 +    public boolean update(Version instance) throws SQLException {
    6.92 +        Objects.requireNonNull(instance.getName());
    6.93 +        update.setString(1, instance.getName());
    6.94 +        update.setInt(2, instance.getOrdinal());
    6.95 +        update.setString(3, instance.getStatus().name());
    6.96 +        update.setInt(4, instance.getId());
    6.97 +        return update.executeUpdate() > 0;
    6.98 +    }
    6.99 +
   6.100 +    @Override
   6.101 +    public List<Version> list(Project project) throws SQLException {
   6.102 +        list.setInt(1, project.getId());
   6.103 +        List<Version> versions = new ArrayList<>();
   6.104 +        try (var result = list.executeQuery()) {
   6.105 +            while (result.next()) {
   6.106 +                versions.add(mapColumns(result));
   6.107 +            }
   6.108 +        }
   6.109 +        return versions;
   6.110 +    }
   6.111 +
   6.112 +    @Override
   6.113 +    public Version find(int id) throws SQLException {
   6.114 +        find.setInt(1, id);
   6.115 +        try (var result = find.executeQuery()) {
   6.116 +            if (result.next()) {
   6.117 +                return mapColumns(result);
   6.118 +            } else {
   6.119 +                return null;
   6.120 +            }
   6.121 +        }
   6.122 +    }
   6.123 +}
     7.1 --- a/src/main/java/de/uapcore/lightpit/entities/Version.java	Sun May 17 16:00:13 2020 +0200
     7.2 +++ b/src/main/java/de/uapcore/lightpit/entities/Version.java	Sun May 17 16:23:39 2020 +0200
     7.3 @@ -33,21 +33,27 @@
     7.4  public class Version implements Comparable<Version> {
     7.5  
     7.6      private final int id;
     7.7 +    private final Project project;
     7.8      private String name;
     7.9      /**
    7.10       * If we do not want versions to be ordered lexicographically we may specify an order.
    7.11       */
    7.12 -    private int ordinal;
    7.13 -    private VersionStatus status;
    7.14 +    private int ordinal = 0;
    7.15 +    private VersionStatus status = VersionStatus.Future;
    7.16  
    7.17 -    public Version(int id) {
    7.18 +    public Version(int id, Project project) {
    7.19          this.id = id;
    7.20 +        this.project = project;
    7.21      }
    7.22  
    7.23      public int getId() {
    7.24          return id;
    7.25      }
    7.26  
    7.27 +    public Project getProject() {
    7.28 +        return project;
    7.29 +    }
    7.30 +
    7.31      public String getName() {
    7.32          return name;
    7.33      }
     8.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Sun May 17 16:00:13 2020 +0200
     8.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Sun May 17 16:23:39 2020 +0200
     8.3 @@ -33,10 +33,17 @@
     8.4  import de.uapcore.lightpit.dao.DataAccessObjects;
     8.5  import de.uapcore.lightpit.entities.Project;
     8.6  import de.uapcore.lightpit.entities.User;
     8.7 +import de.uapcore.lightpit.entities.Version;
     8.8 +import de.uapcore.lightpit.entities.VersionStatus;
     8.9 +import org.slf4j.Logger;
    8.10 +import org.slf4j.LoggerFactory;
    8.11  
    8.12  import javax.servlet.annotation.WebServlet;
    8.13  import javax.servlet.http.HttpServletRequest;
    8.14 +import javax.servlet.http.HttpServletResponse;
    8.15 +import java.io.IOException;
    8.16  import java.sql.SQLException;
    8.17 +import java.util.NoSuchElementException;
    8.18  import java.util.Optional;
    8.19  
    8.20  import static de.uapcore.lightpit.Functions.fqn;
    8.21 @@ -52,9 +59,11 @@
    8.22  )
    8.23  public final class ProjectsModule extends AbstractLightPITServlet {
    8.24  
    8.25 +    private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
    8.26 +
    8.27      public static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected-project");
    8.28  
    8.29 -    @RequestMapping(method = HttpMethod.GET)
    8.30 +    @RequestMapping(method = HttpMethod.GET, menuKey = "menu.index")
    8.31      public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
    8.32          final var projectList = dao.getProjectDao().list();
    8.33          req.setAttribute("projects", projectList);
    8.34 @@ -115,18 +124,80 @@
    8.35  
    8.36              setRedirectLocation(req, "./projects/");
    8.37              setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
    8.38 -        } catch (NullPointerException | NumberFormatException | SQLException ex) {
    8.39 +            LOG.debug("Successfully updated project {}", project.getName());
    8.40 +        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
    8.41              // TODO: set request attribute with error text
    8.42              req.setAttribute("project", project);
    8.43              setDynamicFragment(req, "project-form");
    8.44 +            LOG.warn("Form validation failure: {}", ex.getMessage());
    8.45 +            LOG.debug("Details:", ex);
    8.46          }
    8.47  
    8.48          return ResponseType.HTML;
    8.49      }
    8.50  
    8.51 +    @RequestMapping(requestPath = "versions", method = HttpMethod.GET, menuKey = "menu.versions")
    8.52 +    public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
    8.53 +        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
    8.54 +        if (selectedProject == null) {
    8.55 +            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
    8.56 +            return ResponseType.NONE;
    8.57 +        }
    8.58  
    8.59 -    @RequestMapping(requestPath = "versions", method = HttpMethod.GET, menuKey = "menu.versions")
    8.60 -    public ResponseType versions(HttpServletRequest req, DataAccessObjects dao) {
    8.61 +        req.setAttribute("versions", dao.getVersionDao().list(selectedProject));
    8.62 +        setDynamicFragment(req, "versions");
    8.63 +
    8.64 +        return ResponseType.HTML;
    8.65 +    }
    8.66 +
    8.67 +    @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
    8.68 +    public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
    8.69 +        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
    8.70 +        if (selectedProject == null) {
    8.71 +            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
    8.72 +            return ResponseType.NONE;
    8.73 +        }
    8.74 +
    8.75 +        Optional<Integer> id = getParameter(req, Integer.class, "id");
    8.76 +        if (id.isPresent()) {
    8.77 +            req.setAttribute("version", Optional.ofNullable(dao.getVersionDao().find(id.get())).orElse(new Version(-1, selectedProject)));
    8.78 +        } else {
    8.79 +            req.setAttribute("version", new Version(-1, selectedProject));
    8.80 +        }
    8.81 +        req.setAttribute("versionStatusEnum", VersionStatus.values());
    8.82 +
    8.83 +        setDynamicFragment(req, "version-form");
    8.84 +
    8.85 +        return ResponseType.HTML;
    8.86 +    }
    8.87 +
    8.88 +    @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
    8.89 +    public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
    8.90 +        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
    8.91 +        if (selectedProject == null) {
    8.92 +            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
    8.93 +            return ResponseType.NONE;
    8.94 +        }
    8.95 +
    8.96 +        Version version = new Version(-1, selectedProject);
    8.97 +        try {
    8.98 +            version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject);
    8.99 +            version.setName(getParameter(req, String.class, "name").orElseThrow());
   8.100 +            getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
   8.101 +            version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
   8.102 +            dao.getVersionDao().saveOrUpdate(version);
   8.103 +
   8.104 +            setRedirectLocation(req, "./projects/versions/");
   8.105 +            setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
   8.106 +            LOG.debug("Successfully updated version {} for project {}", version.getName(), selectedProject.getName());
   8.107 +        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
   8.108 +            // TODO: set request attribute with error text
   8.109 +            req.setAttribute("version", version);
   8.110 +            req.setAttribute("versionStatusEnum", VersionStatus.values());
   8.111 +            setDynamicFragment(req, "version-form");
   8.112 +            LOG.warn("Form validation failure: {}", ex.getMessage());
   8.113 +            LOG.debug("Details:", ex);
   8.114 +        }
   8.115  
   8.116          return ResponseType.HTML;
   8.117      }
     9.1 --- a/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Sun May 17 16:00:13 2020 +0200
     9.2 +++ b/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Sun May 17 16:23:39 2020 +0200
     9.3 @@ -32,10 +32,13 @@
     9.4  import de.uapcore.lightpit.*;
     9.5  import de.uapcore.lightpit.dao.DataAccessObjects;
     9.6  import de.uapcore.lightpit.entities.User;
     9.7 +import org.slf4j.Logger;
     9.8 +import org.slf4j.LoggerFactory;
     9.9  
    9.10  import javax.servlet.annotation.WebServlet;
    9.11  import javax.servlet.http.HttpServletRequest;
    9.12  import java.sql.SQLException;
    9.13 +import java.util.NoSuchElementException;
    9.14  import java.util.Optional;
    9.15  
    9.16  @LightPITModule(
    9.17 @@ -49,6 +52,8 @@
    9.18  )
    9.19  public final class UsersModule extends AbstractLightPITServlet {
    9.20  
    9.21 +    private static final Logger LOG = LoggerFactory.getLogger(UsersModule.class);
    9.22 +
    9.23      @RequestMapping(method = HttpMethod.GET)
    9.24      public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
    9.25          final var userDao = dao.getUserDao();
    9.26 @@ -90,10 +95,14 @@
    9.27  
    9.28              setRedirectLocation(req, "./teams/");
    9.29              setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
    9.30 -        } catch (NullPointerException | NumberFormatException | SQLException ex) {
    9.31 +
    9.32 +            LOG.debug("Successfully updated user {}", user.getUsername());
    9.33 +        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
    9.34              // TODO: set request attribute with error text
    9.35              req.setAttribute("user", user);
    9.36              setDynamicFragment(req, "user-form");
    9.37 +            LOG.warn("Form validation failure: {}", ex.getMessage());
    9.38 +            LOG.debug("Details:", ex);
    9.39          }
    9.40  
    9.41          return ResponseType.HTML;
    10.1 --- a/src/main/resources/localization/projects.properties	Sun May 17 16:00:13 2020 +0200
    10.2 +++ b/src/main/resources/localization/projects.properties	Sun May 17 16:23:39 2020 +0200
    10.3 @@ -23,9 +23,11 @@
    10.4  
    10.5  menuLabel=Projects
    10.6  
    10.7 +menu.index=Index
    10.8  menu.versions=Versions
    10.9  
   10.10  button.create=New Project
   10.11 +button.version.create=New Version
   10.12  
   10.13  no-projects=Welcome to LightPIT. Start off by creating a new project!
   10.14  
   10.15 @@ -34,4 +36,15 @@
   10.16  thead.repoUrl=Repository
   10.17  thead.owner=Project Lead
   10.18  
   10.19 +thead.version.name=Version
   10.20 +thead.version.status=Status
   10.21 +thead.version.ordinal=Custom Ordering
   10.22 +tooltip.ordinal=Use to override lexicographic ordering.
   10.23 +
   10.24  placeholder.null-owner=Unassigned
   10.25 +
   10.26 +version.status.Future=Future
   10.27 +version.status.Unreleased=Unreleased
   10.28 +version.status.Released=Released
   10.29 +version.status.LTS=LTS
   10.30 +version.status.Deprecated=Deprecated
   10.31 \ No newline at end of file
    11.1 --- a/src/main/resources/localization/projects_de.properties	Sun May 17 16:00:13 2020 +0200
    11.2 +++ b/src/main/resources/localization/projects_de.properties	Sun May 17 16:23:39 2020 +0200
    11.3 @@ -23,9 +23,11 @@
    11.4  
    11.5  menuLabel=Projekte
    11.6  
    11.7 +menu.index=Index
    11.8  menu.versions=Versionen
    11.9  
   11.10  button.create=Neues Projekt
   11.11 +button.version.create=Neue Version
   11.12  
   11.13  no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes!
   11.14  
   11.15 @@ -34,4 +36,15 @@
   11.16  thead.repoUrl=Repository
   11.17  thead.owner=Projektleitung
   11.18  
   11.19 +thead.version.name=Version
   11.20 +thead.version.status=Status
   11.21 +thead.version.ordinal=Sequenznummer
   11.22 +tooltip.ordinal=\u00dcbersteuert die lexikographische Sortierung.
   11.23 +
   11.24  placeholder.null-owner=Nicht Zugewiesen
   11.25 +
   11.26 +version.status.Future=Geplant
   11.27 +version.status.Unreleased=Unver\u00f6ffentlicht
   11.28 +version.status.Released=Ver\u00f6ffentlicht
   11.29 +version.status.LTS=Langzeitsupport
   11.30 +version.status.Deprecated=Veraltet
    12.1 --- a/src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp	Sun May 17 16:00:13 2020 +0200
    12.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp	Sun May 17 16:23:39 2020 +0200
    12.3 @@ -69,7 +69,7 @@
    12.4          <tr>
    12.5              <td colspan="2">
    12.6                  <input type="hidden" name="id" value="${project.id}" />
    12.7 -                <a href="./${moduleInfo.modulePath}" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
    12.8 +                <a href="./${moduleInfo.modulePath}/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
    12.9                  <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
   12.10              </td>
   12.11          </tr>
    13.1 --- a/src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp	Sun May 17 16:00:13 2020 +0200
    13.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp	Sun May 17 16:23:39 2020 +0200
    13.3 @@ -46,7 +46,7 @@
    13.4  </div>
    13.5  
    13.6  <c:if test="${not empty projects}">
    13.7 -<table id="project-list" class="datatable medskip">
    13.8 +<table id="project-list" class="datatable medskip fullwidth">
    13.9      <colgroup>
   13.10          <col>
   13.11          <col style="width: 10%">
   13.12 @@ -67,7 +67,7 @@
   13.13      <c:forEach var="project" items="${projects}">
   13.14          <tr class="nowrap" <c:if test="${project eq selectedProject}">data-selected</c:if> >
   13.15              <td style="width: 2em;"><a href="./${moduleInfo.modulePath}/edit?id=${project.id}">&#x270e;</a></td>
   13.16 -            <td><a href="./${moduleInfo.modulePath}?select=${project.id}"><c:out value="${project.name}"/></a></td>
   13.17 +            <td><a href="./${moduleInfo.modulePath}/?select=${project.id}"><c:out value="${project.name}"/></a></td>
   13.18              <td><c:out value="${project.description}"/></td>
   13.19              <td>
   13.20                  <c:if test="${not empty project.repoUrl}">
    14.1 --- a/src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp	Sun May 17 16:00:13 2020 +0200
    14.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp	Sun May 17 16:23:39 2020 +0200
    14.3 @@ -61,7 +61,7 @@
    14.4          <tr>
    14.5              <td colspan="2">
    14.6                  <input type="hidden" name="userid" value="${user.id}" />
    14.7 -                <a href="./${moduleInfo.modulePath}" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
    14.8 +                <a href="./${moduleInfo.modulePath}/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
    14.9                  <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
   14.10              </td>
   14.11          </tr>
    15.1 --- a/src/main/webapp/WEB-INF/dynamic_fragments/users.jsp	Sun May 17 16:00:13 2020 +0200
    15.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/users.jsp	Sun May 17 16:23:39 2020 +0200
    15.3 @@ -44,7 +44,7 @@
    15.4  </div>
    15.5  
    15.6  <c:if test="${not empty users}">
    15.7 -    <table class="datatable medskip" style="width: auto">
    15.8 +    <table class="datatable medskip">
    15.9          <thead>
   15.10          <tr>
   15.11              <th></th>
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/version-form.jsp	Sun May 17 16:23:39 2020 +0200
    16.3 @@ -0,0 +1,76 @@
    16.4 +<%--
    16.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    16.6 +
    16.7 +Copyright 2018 Mike Becker. All rights reserved.
    16.8 +
    16.9 +Redistribution and use in source and binary forms, with or without
   16.10 +modification, are permitted provided that the following conditions are met:
   16.11 +
   16.12 +1. Redistributions of source code must retain the above copyright
   16.13 +notice, this list of conditions and the following disclaimer.
   16.14 +
   16.15 +2. Redistributions in binary form must reproduce the above copyright
   16.16 +notice, this list of conditions and the following disclaimer in the
   16.17 +documentation and/or other materials provided with the distribution.
   16.18 +
   16.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   16.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   16.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   16.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   16.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   16.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   16.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   16.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   16.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   16.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   16.29 +--%>
   16.30 +<%@page pageEncoding="UTF-8" %>
   16.31 +<%@page import="de.uapcore.lightpit.Constants" %>
   16.32 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   16.33 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   16.34 +
   16.35 +<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
   16.36 +<c:set scope="page" var="selectedProject" value="${sessionScope[ProjectsModule.SESSION_ATTR_SELECTED_PROJECT]}"/>
   16.37 +
   16.38 +<jsp:useBean id="version" type="de.uapcore.lightpit.entities.Version" scope="request"/>
   16.39 +<jsp:useBean id="versionStatusEnum" type="de.uapcore.lightpit.entities.VersionStatus[]" scope="request" />
   16.40 +
   16.41 +<form action="./${moduleInfo.modulePath}/versions/commit" method="post">
   16.42 +    <table class="formtable" style="width: 35ch">
   16.43 +        <colgroup>
   16.44 +            <col>
   16.45 +            <col style="width: 100%">
   16.46 +        </colgroup>
   16.47 +        <tbody>
   16.48 +        <tr>
   16.49 +            <th><fmt:message key="thead.version.name"/></th>
   16.50 +            <td><input name="name" type="text" maxlength="20" required value="${version.name}" /> </td>
   16.51 +        </tr>
   16.52 +        <tr>
   16.53 +            <th><fmt:message key="thead.version.status"/></th>
   16.54 +            <td>
   16.55 +                <select name="status" required>
   16.56 +                    <c:forEach var="elem" items="${versionStatusEnum}">
   16.57 +                        <option value="${elem}"><fmt:message key="version.status.${elem}" /> </option>
   16.58 +                    </c:forEach>
   16.59 +                </select>
   16.60 +            </td>
   16.61 +        </tr>
   16.62 +        <tr title="<fmt:message key="tooltip.ordinal" />">
   16.63 +            <th><fmt:message key="thead.version.ordinal"/></th>
   16.64 +            <td>
   16.65 +                <input name="ordinal" type="number" min="0" value="${version.ordinal}" />
   16.66 +            </td>
   16.67 +        </tr>
   16.68 +        </tbody>
   16.69 +        <tfoot>
   16.70 +        <tr>
   16.71 +            <td colspan="2">
   16.72 +                <input type="hidden" name="id" value="${version.id}" />
   16.73 +                <a href="./${moduleInfo.modulePath}/versions/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
   16.74 +                <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
   16.75 +            </td>
   16.76 +        </tr>
   16.77 +        </tfoot>
   16.78 +    </table>
   16.79 +</form>
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/src/main/webapp/WEB-INF/dynamic_fragments/versions.jsp	Sun May 17 16:23:39 2020 +0200
    17.3 @@ -0,0 +1,68 @@
    17.4 +<%--
    17.5 +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    17.6 +
    17.7 +Copyright 2018 Mike Becker. All rights reserved.
    17.8 +
    17.9 +Redistribution and use in source and binary forms, with or without
   17.10 +modification, are permitted provided that the following conditions are met:
   17.11 +
   17.12 +1. Redistributions of source code must retain the above copyright
   17.13 +notice, this list of conditions and the following disclaimer.
   17.14 +
   17.15 +2. Redistributions in binary form must reproduce the above copyright
   17.16 +notice, this list of conditions and the following disclaimer in the
   17.17 +documentation and/or other materials provided with the distribution.
   17.18 +
   17.19 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   17.20 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   17.21 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   17.22 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   17.23 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   17.24 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   17.25 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   17.26 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   17.27 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   17.28 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   17.29 +--%>
   17.30 +<%@page pageEncoding="UTF-8" %>
   17.31 +<%@page import="de.uapcore.lightpit.Constants" %>
   17.32 +<%@page import="de.uapcore.lightpit.modules.ProjectsModule" %>
   17.33 +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
   17.34 +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   17.35 +
   17.36 +<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
   17.37 +<c:set scope="page" var="selectedProject" value="${sessionScope[ProjectsModule.SESSION_ATTR_SELECTED_PROJECT]}"/>
   17.38 +
   17.39 +<jsp:useBean id="versions" type="java.util.List<de.uapcore.lightpit.entities.Version>" scope="request"/>
   17.40 +
   17.41 +<c:if test="${empty selectedProject}">
   17.42 +    <div class="info-box">
   17.43 +    <fmt:message key="no-projects" />
   17.44 +    </div>
   17.45 +</c:if>
   17.46 +<c:if test="${not empty selectedProject}">
   17.47 +<div id="tool-area">
   17.48 +    <a href="./${moduleInfo.modulePath}/versions/edit" class="button"><fmt:message key="button.version.create" /></a>
   17.49 +</div>
   17.50 +
   17.51 +<c:if test="${not empty versions}">
   17.52 +<table id="project-list" class="datatable medskip">
   17.53 +    <thead>
   17.54 +    <tr>
   17.55 +        <th></th>
   17.56 +        <th><fmt:message key="thead.version.name"/></th>
   17.57 +        <th><fmt:message key="thead.version.status"/></th>
   17.58 +    </tr>
   17.59 +    </thead>
   17.60 +    <tbody>
   17.61 +    <c:forEach var="version" items="${versions}">
   17.62 +        <tr class="nowrap" >
   17.63 +            <td style="width: 2em;"><a href="./${moduleInfo.modulePath}/versions/edit?id=${version.id}">&#x270e;</a></td>
   17.64 +            <td><c:out value="${version.name}"/></td>
   17.65 +            <td><fmt:message key="version.status.${version.status}" /></td>
   17.66 +        </tr>
   17.67 +    </c:forEach>
   17.68 +    </tbody>
   17.69 +</table>
   17.70 +</c:if>
   17.71 +</c:if>
    18.1 --- a/src/main/webapp/lightpit.css	Sun May 17 16:00:13 2020 +0200
    18.2 +++ b/src/main/webapp/lightpit.css	Sun May 17 16:23:39 2020 +0200
    18.3 @@ -117,7 +117,7 @@
    18.4  }
    18.5  
    18.6  table.datatable {
    18.7 -    width: 100%;
    18.8 +    width: auto;
    18.9      border-style: solid;
   18.10      border-width: 1pt;
   18.11      border-color: black;

mercurial