public class DBFunctions {

    public static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyyMMdd");
    public static final List<Class<Integer>> INT_TYPES = asList(Integer.class, Integer.TYPE);
    public static final List<Class<Long>> LONG_TYPES = asList(Long.class, Long.TYPE);

    public static ComboPooledDataSource pool;

    public static void setupConnectionPool(String connectionString, String username, String password, int maxConnections) {
        try {
            pool = new ComboPooledDataSource();
            pool.setDriverClass("org.postgresql.Driver");
            pool.setJdbcUrl(connectionString);
            pool.setUser(username);
            pool.setPassword(password);
            pool.setInitialPoolSize(maxConnections / 2);
            pool.setMaxPoolSize(maxConnections);
            pool.setMinPoolSize(1);
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }

    public static Connection getConnection() {
        try {
            Connection connection = pool.getConnection();
            connection.setAutoCommit(false);
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static String makeSelect(Class<?> clazz, Collection<OrderBy> orderBy, Where... whereClause) {
        Set<String> sql = new HashSet<>();
        List<Column> allColumns = getMappedFields(clazz).stream().map(field -> field.getAnnotation(Column.class)).collect(toList());
        Stream<Column> noAggregation = allColumns.stream().filter(col -> col.function() == Function.NONE);
        Stream<Column> aggregated = allColumns.stream().filter(col -> col.function() != Function.NONE);

        noAggregation.forEach(col -> sql.add(col.column().name()));
        aggregated.forEach(col -> sql.add(col.function() + "(" + col.column().name() + ") as " + col.column().name()));
        asList(whereClause).forEach(where -> sql.add(where.column.name()));
        orderBy.forEach(order -> sql.add(order.getColumn().name()));

        return "SELECT DISTINCT " + join(sql, ", ");
    }

    public static String makeWhere(Collection<Join> joins, Where... whereClause) {
        List<String> whereParts = new ArrayList<>();

        if (whereClause != null) {
            asList(whereClause).forEach(where ->
                    whereParts.add(where.column.name() + " " + where.operator + (where.value != null ? " ?" : "")));
        }
        if (!joins.isEmpty()) {
            joins.forEach(join -> whereParts.add(join.foreign.name() + " = " + join.primary.name()));
        }

        return whereParts.isEmpty() ? "" : "WHERE " + join(whereParts, " AND ");
    }

    public static <T> String makeGroupBy(Class<T> clazz, Collection<OrderBy> orderBy) {
        List<Field> mappedFields = getMappedFields(clazz);
        List<String> noAggregation = mappedFields.stream()
                .map(f -> f.getAnnotation(Column.class))
                .filter(col -> col.function() == Function.NONE)
                .map(col -> col.column().name())
                .collect(toList());

        if ((mappedFields.size() == noAggregation.size()) || noAggregation.isEmpty()) return "";

        final List<String> groupBy = new ArrayList<>(noAggregation);
        orderBy.forEach(order -> groupBy.add(order.getColumn().name()));
        return " GROUP BY " + join(groupBy, ", ");
    }

    public static List<Object> createParameterList( Where[] whereClause) {
        if (whereClause == null) return new ArrayList<>();
        return asList(whereClause).stream()
                .filter(where -> where.value != null)
                .map(where -> where.value)
                .collect(toList());
    }

    public static Set<Table> getTables(Class<?> clazz, Where... searchParams) {
        Set<Table> tables = getMappedFields(clazz).stream().map(field -> getColumn(field).getTable()).collect(toSet());

        if (searchParams != null) {
            Stream<Table> tableStream = asList(searchParams).stream()
                    .map(param -> param.column.getTable());
            tables.addAll((Collection<Table>)tableStream.collect(toList()));
        }
        return tables;
    }

    public static List<Field> getMappedFields(Class<?> clazz) {
        List<Field> columnFields = asList(clazz.getDeclaredFields()).stream()
                .filter(f -> f.isAnnotationPresent(Column.class))
                .collect(toList());
        columnFields.forEach(f->f.setAccessible(true));

        if (clazz.getSuperclass() != null) {
            columnFields.addAll(getMappedFields(clazz.getSuperclass()));
        }
        return columnFields;
    }

    public static Collection<Join> findJoins(Collection<Table> tables) {
        Set<Join> joins = new HashSet<>();
        Set<Pair<Table, Table>> alreadyTried = new HashSet<>();
        for (Table a : tables) {
            for (Table b: tables) {
                if ( a == b || !alreadyTried.add(new Pair<>(a, b))) continue;
                Stream<Join> jonStream = new DijkstraShortestPath<>(tableGraph, a, b).getPathEdgeList()
                        .stream().map(joinEdge -> joinEdge.join);
                joins.addAll((Collection<Join>)jonStream.collect(toList()));
            }
        }
        return joins;
    }

    private static final SimpleWeightedGraph<Table, JoinEdge> tableGraph = new SimpleWeightedGraph<Table, JoinEdge>(JoinEdge.class) {{
        asList(Table.values()).forEach(this::addVertex);
        asList(DatabaseColumns.values()).stream()
                .filter(col -> col.getType() == ForeignKey)
                .forEach(col -> {
                    Join join = new Join(getPrimaryKey(col.getJoinedTo()).get(), col);
                    JoinEdge joinEdge = new JoinEdge(join);
                    addEdge(col.getTable(), col.getJoinedTo(), joinEdge);

                    /*
                    the child table is linked to DaycareCenter via a foreign key
                    So is the grownup table. The child and grownup tables are linked together
                    via a many-to-many join table. When finding the shortest path from child to
                    grownup, I want it to find the many-to-many link, not the path via
                    daycare center. Without a weighted graph, the two paths would be seen
                    as equally long.  Giving many-to-many edges half the weight of others,
                    leads to it preferring the many-to-many link in this case. As it should.
                     */

                    boolean manyToManyLink = !getPrimaryKey(col.getTable()).isPresent();
                    int weight = manyToManyLink ? 1 : 2;
                    setEdgeWeight(joinEdge, weight);
                });
    }};

    private static class JoinEdge extends DefaultWeightedEdge {
        private final Join join;
        public JoinEdge(Join join) {
            this.join = join;
        }
    }

    public static DatabaseColumns getColumn(Field f) {
        return f.getAnnotation(Column.class).column();
    }

    public static <T> Table getMainTable(T newOne) {
        return getMainTableForClass(newOne.getClass());
    }

    public static <T> Table getMainTableForClass(Class<T> clazz) {
        List<Table> pks = getMappedFields(clazz).stream()
                .filter(f -> getColumn(f).getType() == PrimaryKey)
                .map(f-> getColumn(f).getTable())
                .collect(toList());
        if (pks.size() > 1) {
            throw new IllegalArgumentException("Multiple primary keys are referenced in class " + clazz + ", must specify which table to do the insert on");
        }
        return pks.get(0);
    }


    public static <T> Field getPrimaryKeyField(T obj) {
        return getMappedFields(obj.getClass()).stream().filter(f-> getColumn(f).getType() == PrimaryKey).findFirst().orElseThrow(() -> new IllegalArgumentException(obj.getClass().getName() + " has no primary key defined"));
    }




    public static <A, B> Optional<Table> findManyToManyTable(A from, B to) {
        Collection<Join> joins = findJoins(asList(getMainTable(from), getMainTable(to)));
        for (Join j : joins) {
            boolean manyToManyLinkTable = !DatabaseColumns.getPrimaryKey(j.foreign.getTable()).isPresent();
            if (manyToManyLinkTable) return Optional.of(j.foreign.getTable());
        }
        return Optional.empty();
    }

    public static void set(Field f, Object instance, Object newValue) {
        try {
            f.set(instance, newValue);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object get(Field f, Object instance) {
        try {
            return f.get(instance);
        }catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
} public class ChildCRUD extends WithDatabase {

    public void newChild(Child newOne) {
        getDB().insert(newOne);
    }

    public void updateChild(Child updated) {
        getDB().update(updated);
    }

    public Child findChild(long grownupId, int childId) {
        return getDB().selectOnlyOne(Child.class,
                new Where(child_id, " = ", childId),
                new Where(gc_grownup_id, " = ",grownupId)).orElseThrow(
                () -> new IllegalArgumentException("No permission / No child found with id " + childId));
    }
} public class ChildServlet extends HttpServlet {

    private final ChildCRUD dbActions;

    public ChildServlet(ChildCRUD dbActions) {
        this.dbActions = dbActions;
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getParameter("action");
        if  ("update".equals(action)) {
            update(request, response);
        }else if ("insert".equals(action)){
            insert(request, response);
        } else {
            response.getWriter().print("no action specified");
        }
    }

    private void insert(HttpServletRequest request, HttpServletResponse response) throws IOException {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        String nickname = request.getParameter("nickname");
        String firstName = request.getParameter("child_first_name");
        String middleName = request.getParameter("child_middle_name");
        String lastName = request.getParameter("child_last_name");
        String color = request.getParameter("color");
        long dayCareId = Long.parseLong(request.getParameter("child_daycare_id"));
        if (currentUser.getGrownup().getDayCareId() != dayCareId) {
            System.err.println(currentUser.getGrownup().getEmail() + " cannot insert children into daycare " + dayCareId);
            return;
        }

        Child newChild = new Child();
        newChild.setDaycareId(dayCareId);
        newChild.setNickname(nickname);
        newChild.setFirstName(firstName);
        newChild.setLastName(lastName);
        newChild.setMiddleName(middleName);
        newChild.setColor(color);

        dbActions.newChild(newChild);
        response.sendRedirect("/editChild.html?ChildId=" + newChild.getChildId() + "&message=updated");
    }

    private void update(HttpServletRequest request, HttpServletResponse response) throws IOException {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        final int childId = Integer.parseInt(request.getParameter("child_id"));
        Child updated = dbActions.findChild(currentUser.getGrownUpId(), childId);

        String nickname = request.getParameter("nickname");
        String firstName = request.getParameter("child_first_name");
        String middleName = request.getParameter("child_middle_name");
        String lastName = request.getParameter("child_last_name");
        String color = request.getParameter("color");

        updated.setNickname(nickname);
        updated.setFirstName(firstName);
        updated.setLastName(lastName);
        updated.setMiddleName(middleName);
        updated.setColor(color);

        dbActions.updateChild(updated);
        response.sendRedirect("/editChild.html?" + child_id.name() + "=" + childId + "&message=updated");
    }



} public class GrownUpCRUD extends WithDatabase {

    public void newGrownup(GrownUp newOne, String[] childIds, String[] clubIds) {
        DB db = getDB();
        db.insert(newOne);
        List<ChildPK> children = asList(childIds).stream().map(id -> new ChildPK(Long.parseLong(id))).collect(toList());
        children.forEach(child -> db.link(newOne, child));

        List<ClubPK> clubs = asList(clubIds).stream().map(id -> new ClubPK(Long.parseLong(id))).collect(toList());
        clubs.forEach(club -> db.link(newOne, club));
    }

    public void updateGrownup(GrownUp updated, String[] childIds, String[] clubIds) {
        DB db = getDB();
        db.update(updated);

        List<ChildPK> existing = db.select(ChildPK.class, new Where(gc_grownup_id, " = ", updated.getGrownUpId()));
        List<ChildPK> newOnes = asList(childIds).stream().map(id -> new ChildPK(Long.parseLong(id))).collect(toList());

        subtract(existing, newOnes).forEach(child -> db.unlink(updated, child));
        subtract(newOnes, existing).forEach(child -> db.link(updated, child));

        List<ClubPK> existingClubs = db.select(ClubPK.class, new Where(grg_grownup_id, " = ", updated.getGrownUpId()));
        List<ClubPK> newClubs = asList(clubIds).stream().map(id -> new ClubPK(Long.parseLong(id))).collect(toList());

        subtract(existingClubs, newClubs).forEach(club -> db.unlink(updated, club));
        subtract(newClubs, existingClubs).forEach(club -> db.link(updated, club));

    }

    public GrownUp getGrownup(int grownUpId) {
        return getDB().selectOnlyOne(GrownUp.class, new Where(grownup_id, " = ", grownUpId))
                .orElseThrow(() -> new IllegalArgumentException("No grown up found with id " + grownUpId));
    }

} public class GrownUpServlet extends HttpServlet {

    private final GrownUpCRUD dbActions;

    public GrownUpServlet(GrownUpCRUD dbActions) {
        this.dbActions = dbActions;
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getParameter("action");
        switch (action) {
            case "insert" : insert(request, response); break;
            case "update" : update(request, response); break;
            default: throw new IllegalArgumentException("no valid action supplied");
        }
    }

    private void insert(HttpServletRequest request, HttpServletResponse response) throws IOException {
        long daycare = Integer.parseInt(request.getParameter("grownup_daycare_id"));
        String email = request.getParameter(DatabaseColumns.email.name());
        String password = request.getParameter(DatabaseColumns.password.name());
        String firstName = request.getParameter(grownup_first_name.name());
        String middleName = request.getParameter(grownup_middle_name.name());
        String lastName = request.getParameter(grownup_last_name.name());
        String[] clubIds = request.getParameterValues(club_id.name());
        String[] childIds = request.getParameterValues(child_id.name());

        GrownUp newOne = new GrownUp();
        newOne.setEmail(email);
        newOne.setPassword(password);
        newOne.setDayCareId(daycare);
        newOne.setLastName(lastName);
        newOne.setMiddleName(middleName);
        newOne.setFirstName(firstName);

        dbActions.newGrownup(newOne, childIds, clubIds);
        response.sendRedirect("/index.html?message=" + URLEncoder.encode("new user added", "UTF-8" ));
    }

    private void update(HttpServletRequest request, HttpServletResponse response) throws IOException {
        int grownUpId = Integer.parseInt(request.getParameter("grownup_id"));
        String email = request.getParameter("email");
        String password = request.getParameter("password");
        String firstName = request.getParameter(grownup_first_name.name());
        String middleName = request.getParameter(grownup_middle_name.name());
        String lastName = request.getParameter(grownup_last_name.name());
        String version = request.getParameter(grownup_version.name());
        String[] clubIds = request.getParameterValues(club_id.name());
        String[] childIds = request.getParameterValues(child_id.name());

        GrownUp updated = dbActions.getGrownup(grownUpId);
        updated.setEmail(email);
        updated.setPassword(password);
        updated.setLastName(lastName);
        updated.setMiddleName(middleName);
        updated.setFirstName(firstName);
        updated.setVersion(Integer.parseInt(version));

        dbActions.updateGrownup(updated, childIds, clubIds);

        response.sendRedirect("/editGrownup.html?" + grownup_id.name() + "=" + grownUpId);
    }



} public class OverviewCRUD extends WithDatabase{

    
    public void updateSchedule(Long childId, String scheduleName) {
        Schedule schedule = getDB().selectOnlyOne(Schedule.class,
                new Where(schedule_child_id, " = ", childId),
                new Where(schedule_name, " Like ", scheduleName))
                .orElseThrow(()->new IllegalArgumentException("Child " + childId + " has no schedule named " + scheduleName));
        schedule.setLastEventTime(System.currentTimeMillis());
        getDB().update(schedule);
    }


    public OverviewOfChild getChildWithSchedules(Long childId) {
        OverviewOfChild child = getDB()
                .selectOnlyOne(OverviewOfChild.class, new Where(child_id, " = ", childId))
                .orElseThrow(() -> new IllegalArgumentException("child " + childId + " not found"));
        child.addSchedules(getDB().select(Schedule.class, new Where(schedule_child_id, " = ", childId)));
        return child;
    }


    public List<OverviewOfChild> getOverviewOfChildren(long grownupId) {
        DB db = getDB();
        List<OverviewOfChild> children = db.select(OverviewOfChild.class, new OrderBy(nickname), new Where(gc_grownup_id, " = ", grownupId));
        children.forEach(child -> child.addSchedules(db.select(Schedule.class, new Where(schedule_child_id, " = ", child.getChildId()))));
        children.forEach(child -> child.addGroups(db.select(club_name, String.class, new Where(grc_child_id, " = ", child.getChildId()))));
        return children;
    }

    public List<String> getSchedulesFor(long grownupId) {
        return getDB().select(schedule_name, String.class, new Where(gc_grownup_id, " = ", grownupId));
    }

    public List<Club> getClubsFor(long grownUpId) {
        return getDB().select(Club.class, new Where(grg_grownup_id, " = ", grownUpId));
    }
} public class OverviewServlet extends HttpServlet {

    private OverviewCRUD dbActions;

    private final Map<String, BiFunction<HttpServletRequest, HttpServletResponse, Void>> actionMap = new ConcurrentHashMap<>();

    public OverviewServlet(OverviewCRUD dbActions) {
        this.dbActions = dbActions;
        actionMap.put("poll", this::poll);
        actionMap.put("scheduleUpdate", this::scheduleUpdate);
        actionMap.put("scheduleNames", this::getScheduleNames);
        actionMap.put("children", this::getChildren);
        actionMap.put("clubs", this::getClubs);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestedAction = request.getParameter("action");
        if (actionMap.containsKey(requestedAction)) {
            actionMap.get(requestedAction).apply(request, response);
        }else {
            throw new IllegalArgumentException(requestedAction + "? Don't understand what you mean");
        }
    }

    private Void poll(HttpServletRequest request, HttpServletResponse response) {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        Continuation c = ContinuationSupport.getContinuation(request);
        if (c.isInitial()) {
            c.setTimeout(10000);
            c.suspend(response);
            currentUser.waitForChanges(c);
        } else {
            print(response, new Gson().toJson(new ArrayList<Child>()));
        }
        return null;
    }

    private Void getChildren(HttpServletRequest request, HttpServletResponse response) {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        print(response, new Gson().toJson(dbActions.getOverviewOfChildren(currentUser.getGrownUpId())));
        return null;
    }

    private Void getClubs(HttpServletRequest request, HttpServletResponse response) {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        print(response, new Gson().toJson(dbActions.getClubsFor(currentUser.getGrownUpId())));
        return null;
    }

    private Void getScheduleNames(HttpServletRequest request, HttpServletResponse response) {
        RegisteredUser currentUser = getCurrentUser(request.getSession());
        print(response, new Gson().toJson(dbActions.getSchedulesFor(currentUser.getGrownUpId())));
        return null;
    }

    private Void scheduleUpdate(HttpServletRequest request, HttpServletResponse response) {
        Long childId = Long.valueOf(request.getParameter(child_id.name()));
        String scheduleName = request.getParameter(schedule_name.name());

        dbActions.updateSchedule(childId, scheduleName);
        print(response, new Gson().toJson(asList(dbActions.getChildWithSchedules(childId))));
        return null;
    }

    private void print(HttpServletResponse response, String json) {
        try {
            response.setContentType(Constants.CONTENT_TYPE);
            response.getWriter().print(json);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

} public class TimelineCRUD extends WithDatabase{

    public TimelineCRUD() {
    }

    public TimelineCRUD(DB db) {
        super(db);
    }

    public void insert(Event event, String[] childIds, String[] clubIds, Collection<FileItem> attachments) {
        boolean childrenSpecified = containsNumericData(childIds);
        boolean clubsSpecified = containsNumericData(clubIds);
        if (!childrenSpecified && !clubsSpecified) throw new IllegalArgumentException("No children or groups specified");

        getDB().insert(event);
        attachments.forEach(file -> {
            if (isNotBlank(file.getName())) { //why is this needed?
                Attachment attachment = new Attachment(event.getEventId(), file.getContentType(), file.get());
                attachment.setThumbnail(makeThumb(attachment.getAttachment()));
                getDB().insert(attachment);
            }
        });

        Set<ChildPK> children = new HashSet<>();
        if (childrenSpecified) {
            Stream<ChildPK> pkStream = asList(childIds).stream().map(id -> new ChildPK(parseLong(id)));
            children.addAll((Collection<ChildPK>)pkStream.collect(toList()));
        }

        if (clubsSpecified) {
            List<ClubPK> clubs = asList(clubIds).stream().map(id -> new ClubPK(parseLong(id))).collect(toList());
            clubs.forEach(clubPk -> getDB().link(clubPk, event));
            children.addAll(getDB().select(ChildPK.class, new Where(grc_club_id, " IN (" + join(clubIds, ",") + ")")));
        }
        children.forEach(childPK -> getDB().link(childPK, event));
    }

    private byte[] makeThumb(byte[] attachment) {
        try {
            BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
            img.createGraphics().drawImage(ImageIO.read(new ByteArrayInputStream(attachment)).getScaledInstance(100, 100, Image.SCALE_SMOOTH), 0, 0, null);
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            ImageIO.write(img, "png", output);
            return output.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    boolean containsNumericData(String[] ids) {
        String justNumbers = join(ids, "");
        return isNotBlank(justNumbers) && isNumeric(justNumbers);
    }

    public Event populate(Event e) {
        List<Long> attachments = getDB().select(attachment_id, Long.TYPE, new Where(attachment_event_id, "=", e.getEventId()));
        e.addAttachments(attachments);
        e.addChildren(getDB().select(ChildName.class,
                new Where(ec_event_id, "=", e.getEventId())));
        return e;
    }

    public List<Event> getEvents(long grownup_id) {
        return getDB()
                .select(Event.class, new OrderBy(event_time, DESCENDING), new Where(gc_grownup_id, " = ", grownup_id))
                .stream().map(this::populate).collect(toList());
    }

    public Attachment getAttachment(Long id) {
        return getDB().selectOnlyOne(
                Attachment.class,
                new Where(attachment_id, "=", id))
                .orElseThrow(() -> new IllegalArgumentException("No attachment found with id " + id));
    }

    public byte[] getThumbnail(Long id) {
        return getDB().select(
                DatabaseColumns.thumbnail, byte[].class,
                new Where(attachment_id, "=", id)).get(0);
    }

    public Long getEventCreator(String eventId) {
        List<Long> creators = getDB().select(event_creator, Long.TYPE, new Where(event_id, " = ", Long.parseLong(eventId)));
        return creators.isEmpty()?null:creators.get(0);
    }

    public Event getEvent(String eventId) {
        Optional<Event> event = getDB().selectOnlyOne(Event.class, new Where(event_id, " = ", Long.parseLong(eventId)));
        return event.isPresent() ? populate(event.get()) : null;
    }

    public void deleteEvent(String eventId) {
        long id = Long.parseLong(eventId);
        EventPK eventPk = new EventPK(id);

        getDB().delete(AttachmentPK.class, new Where(attachment_event_id, " = ", id));

        getDB().select(ChildPK.class, new Where(ec_event_id, " = ", id)).forEach(c -> getDB().unlink(c, eventPk));
        getDB().select(ClubPK.class, new Where(ecl_event_id, " = ", id)).forEach(c -> getDB().unlink(c, eventPk));
        getDB().delete(EventPK.class, new Where(event_id, " = ", id));
    }
} public class TimelineServlet extends HttpServlet {

    private static final Logger log = LoggerFactory.getLogger(TimelineServlet.class);
    private final TimelineCRUD actions;

    private final Map<String, BiFunction<HttpServletRequest, HttpServletResponse, Void>> actionMap = new ConcurrentHashMap<>();

    public TimelineServlet(TimelineCRUD actions) {
        this.actions = actions;
        actionMap.put("getEvents", this::getTimeline);
        actionMap.put("getImage", this::getImage);
        actionMap.put("getThumbnail", this::getThumbnail);
        actionMap.put("insert", this::newEvent);
        actionMap.put("getEvent", this::getEvent);
        actionMap.put("deleteEvent", this::deleteEvent);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (!request.getParameterMap().containsKey("action")) {
            uploadFile(request, response);
        } else {
            String action = request.getParameter("action");
            log.debug(action);
            actionMap.get(action).apply(request, response);
        }
    }

    private Void uploadFile(HttpServletRequest request, HttpServletResponse response) {
        try {
            currentUser(request).addFiles(new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    private Void newEvent(HttpServletRequest request, HttpServletResponse response) {
        String eventName = request.getParameter(event_name.name());
        Event newEvent = withNameAndTime(eventName, System.currentTimeMillis());
        newEvent.setEventCreator(currentUser(request).getGrownUpId());
        String[] children = request.getParameter(child_id.name()).split(",");
        String[] clubs = request.getParameter(club_id.name()).split(",");
        actions.insert(newEvent, children, clubs, currentUser(request).getAndRemoveUploadedFiles());
        print(response, new Gson().toJson(actions.populate(newEvent)));
        return null;
    }


    private Void getThumbnail(HttpServletRequest request, HttpServletResponse response)  {
        try {
            String idString = request.getParameter(attachment_id.name());
            //for some reason angular keeps requesting an image with id "{{attachment}}"
            // instead of actually replacing the brackets with a sensible value...
            if (StringUtils.isBlank(idString) || !StringUtils.isNumeric(idString)) return null;

            Long id = Long.parseLong(idString);
            byte[] file = actions.getThumbnail(id);
            if (file != null) {
                response.setContentType("image/png");
                response.getOutputStream().write(file);
            }
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Void getImage(HttpServletRequest request, HttpServletResponse response)  {
        try {
            String idString = request.getParameter(attachment_id.name());
            //for some reason angular keeps requesting an image with id "{{attachment}}"
            // instead of actually replacing the brackets with a sensible value...
            if (StringUtils.isBlank(idString) || !StringUtils.isNumeric(idString)) return null;

            Long id = Long.parseLong(idString);
            Attachment attachment = actions.getAttachment(id);
            byte[] file = attachment.getAttachment();
            response.setContentType(attachment.getContentType());
            response.getOutputStream().write(file);
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Void getTimeline(HttpServletRequest request, HttpServletResponse response) {
        print(response, new Gson().toJson(actions.getEvents(currentUser(request).getGrownUpId())));
        return null;
    }

    private Void getEvent(HttpServletRequest request, HttpServletResponse response) {
        print(response, new Gson().toJson(actions.getEvent(request.getParameter(event_id.name()))));
        return null;
    }

    public Void deleteEvent(HttpServletRequest request, HttpServletResponse response) {
        String eventId = request.getParameter(event_id.name());
        RegisteredUser user = currentUser(request);
        if (!Objects.equals(actions.getEventCreator(eventId), user.getGrownUpId())){
            throw new SecurityException(user.getGrownup().getFullName() + " does not have permission to delete event " + eventId);
        }
        actions.deleteEvent(eventId);
        print(response, "{}");
        return null;
    }

    private RegisteredUser currentUser(HttpServletRequest request) {
        return RegisteredUser.getCurrentUser(request.getSession());
    }

    private void print(HttpServletResponse response, String returnVal) {
        try {
            log.debug(returnVal);
            response.setContentType(CONTENT_TYPE);
            response.getWriter().print(returnVal);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

} @SuppressWarnings("unchecked")
public static <T extends WithDatabase> T transactional(final T dbActions) {

    ProxyFactory pf = new ProxyFactory();
    pf.setSuperclass(dbActions.getClass());
    try {
        return (T) pf.create(null, null, new MethodHandler() {

            @Override
            public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
                DB db = new DB(DBFunctions.getConnection());
                dbActions.setDB(db);
                try {
                    Object returnVal = thisMethod.invoke(dbActions, args);
                    db.commitAndReleaseConnection();
                    return returnVal;
                } catch (InvocationTargetException e) {
                    db.rollback();
                    throw e.getTargetException();
                } catch (Exception e) {
                    db.rollback();
                    throw e;
                }
            }
        });
    } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
        throw new RuntimeException(e);
    }
}
import no.gorman.database.DBFunctions;
import no.gorman.please.child.ChildCRUD;
import no.gorman.please.child.ChildServlet;
import no.gorman.please.grownup.GrownUpCRUD;
import no.gorman.please.grownup.GrownUpServlet;
import no.gorman.please.overview.OverviewCRUD;
import no.gorman.please.overview.OverviewServlet;
import no.gorman.please.timeline.TimelineCRUD;
import no.gorman.please.timeline.TimelineServlet;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.servlet.DispatcherType;
import java.util.EnumSet;
import java.util.Properties;
import static no.gorman.please.utils.Transactional.transactional;
package no.gorman.please;




public class Main {

    public static void main(String[] args) throws Exception {

        Properties props = new Properties();
        props.load(Main.class.getResourceAsStream("/baxter.properties"));
        DBFunctions.setupConnectionPool(props.getProperty("db"), props.getProperty("username"), props.getProperty("password"), Integer.parseInt(props.getProperty("maxNumConnections")));

        Server server = new Server(Integer.valueOf(props.getProperty("PORT")));
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");

        ServletHolder overviewServlet = new ServletHolder(new OverviewServlet(transactional(new OverviewCRUD())));
        overviewServlet.setAsyncSupported(true);
        context.addServlet(overviewServlet, "/overview/*");
        
        ServletHolder loginServlet = new ServletHolder(new LoginServlet());
        context.addServlet(loginServlet, "/login/*");

        ServletHolder dataServlet = new ServletHolder(new DataServlet());
        context.addServlet(dataServlet, "/data/*");

        ServletHolder grownupServlet = new ServletHolder(new GrownUpServlet(transactional(new GrownUpCRUD())));
        context.addServlet(grownupServlet, "/grownup/*");

        ServletHolder childServlet = new ServletHolder(new ChildServlet(transactional(new ChildCRUD())));