/* * Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de) * * Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://creativecommons.org/licenses/by-nc-sa/3.0/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.knurt.fam.template.controller.image.statistics; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import de.knurt.fam.core.aspects.security.auth.SessionAuth; import de.knurt.fam.core.config.FamCalendarConfiguration; import de.knurt.fam.core.config.FamRequestContainer; import de.knurt.fam.core.config.style.FamColors; import de.knurt.fam.core.config.style.FamFonts; import de.knurt.fam.core.model.config.Facility; import de.knurt.fam.core.model.config.FacilityBookable; import de.knurt.fam.core.model.persist.FacilityAvailability; import de.knurt.fam.core.model.persist.booking.Booking; import de.knurt.fam.core.persistence.dao.FamDaoProxy; import de.knurt.fam.core.util.graphics.TextArea; import de.knurt.fam.core.util.graphics.TextCol; import de.knurt.fam.core.util.mvc.RedirectResolver; import de.knurt.fam.core.util.mvc.RequestInterpreter; import de.knurt.fam.core.view.text.FamDateFormat; import de.knurt.heinzelmann.util.graphics.text.StringMetricsGraphics; import de.knurt.heinzelmann.util.graphics.text.TextSplitter; import de.knurt.heinzelmann.util.graphics.text.TextSplitterOnWidth; import de.knurt.heinzelmann.util.time.SimpleTimeFrame; import de.knurt.heinzelmann.util.time.TimeFrame; /** * generate the overview of bookings for a specific week requested. * * @author Daniel Oltmanns * @since 0.20091011 (10/11/2009) */ public class StatisticDisplayOfWeekImageController extends SimplePngImageController { @Override protected Color getBackgroundColor() { return Color.WHITE; } @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { boolean containsDirectAccess = RequestInterpreter.containsDirectAccess("statistics", request); if (SessionAuth.user(request) == null && !containsDirectAccess) { return RedirectResolver.me().login(request, response); } else { this.setImageWidthAndHeight(request); this.calendarStart = Calendar.getInstance(FamRequestContainer.locale()); try { long timestamp = Long.parseLong(RequestInterpreter.getPureDay(request)); if (timestamp > 0) { calendarStart.setTimeInMillis(timestamp); } } catch (NumberFormatException e) { // stay on today silently } catch (NullPointerException e) { // stay on today silently } this.facility = RequestInterpreter.getFacility(request); if (this.calendarStart == null || this.facility == null) { return RedirectResolver.me().home(request, response); } else { if (this.calendarStart.get(Calendar.DAY_OF_WEEK) != this.calendarStart.getFirstDayOfWeek()) { while (this.calendarStart.get(Calendar.DAY_OF_WEEK) != this.calendarStart.getFirstDayOfWeek()) { this.calendarStart.add(Calendar.DAY_OF_YEAR, -1); } } Integer tmp = RequestInterpreter.getFrom(request); if (tmp != null && tmp >= 0 && tmp <= 23) { this.hourStart = tmp.intValue(); } tmp = RequestInterpreter.getTo(request); if (tmp != null && tmp >= 1 && tmp <= 24) { this.hourStop = tmp.intValue(); } if (this.hourStart == this.hourStop) { this.hourStop++; } else if (this.hourStart > this.hourStop) { tmp = this.hourStop; this.hourStop = this.hourStart; this.hourStart = tmp; } this.setOptions(); return super.handleRequest(request, response); } } } private int imageWidth = 1200; private int imageHeight = 750; private Calendar calendarStart; private Facility facility; /** * bookinglabelsOfWeek: Map<dayOfWeek, [map of labels]> map of labels: * Map<positionInDay, list of bookings at this position> */ private Map<Integer, Map<Integer, List<Booking>>> bookinglabelsOfWeek; /** * bookinglabelsOfWeek: Map<dayOfWeek, [map of facility availability]> map * of facility availability: Map<positionInDay, facility availability> */ private Map<Integer, Map<Integer, FacilityAvailability>> dasOfWeek; @Override protected int getImageWidth() { return imageWidth; } @Override protected int getImageHeight() { return imageHeight; } private Font getFontForText() { int fontSize = 10 * this.getImageWidth() / 800; if (fontSize < 10) { fontSize = 10; } return FamFonts.getFont(Font.PLAIN, fontSize); } @Override protected void createImage(Graphics2D g2d) { g2d.setFont(this.getFontForText()); this.printTimeLine(g2d); this.printWeekLine(g2d); this.printDaysPanel(g2d); this.printHourLinesHorizontal(g2d); this.printDaysStripe(g2d); this.printDaysText(g2d); this.printLinesVertical(g2d); this.printTopBlackLine(g2d); this.printInfo(g2d); } private void setImageWidthAndHeight(HttpServletRequest request) { Integer imageWidthInRq = null; try { imageWidthInRq = Integer.parseInt(request.getParameter("width")); } catch (NumberFormatException e) { } catch (NullPointerException e) { } if (imageWidthInRq != null) { this.imageWidth = Math.abs(imageWidthInRq.intValue()); if (this.imageWidth > 2500) { // do not fire up the server this.imageWidth = 2500; } } Integer imageHeightInRq = null; try { imageHeightInRq = Integer.parseInt(request.getParameter("height")); } catch (NumberFormatException e) { } catch (NullPointerException e) { } if (imageHeightInRq != null) { this.imageHeight = Math.abs(imageHeightInRq.intValue()); if (this.imageHeight > 2500) { // do not fire up the server this.imageHeight = 2500; } } } private int getTimeLineWidth() { return 40; } private int getTimeLineHeight() { return this.getImageHeight() - this.getWeekLineHeight(); } private int hourStop = FamCalendarConfiguration.hourStop(); private int hourStart = FamCalendarConfiguration.hourStart(); private int getWeekLineHeight() { int ground = 20; int hShown = this.hourStop - this.hourStart; int isPpH = Math.round((this.getImageHeight() - ground) / hShown); return this.getImageHeight() - (isPpH * hShown); } private int getTimeLineX() { return PADDING_LEFT; } private int getTimeLineY() { return this.getWeekLineHeight(); } private int getWeekLineWidth() { return this.getImageWidth() - this.getTimeLineWidth(); } private int getPixelPerHour() { float pure = this.getDayHeight() / (this.hourStop - this.hourStart); return Math.round(pure); } private void printTimeLine(Graphics2D g2d) { g2d.setColor(Color.BLACK); int hour = this.hourStart + 1; int offset = 5 + this.getTimeLineY(); float yPointer = offset + this.getPixelPerHour(); g2d.setColor(FamColors.FONT); while (hour < this.hourStop) { // draw hour (in 0-24 h format) g2d.drawString(FamDateFormat.getShortTimeFormatted(hour) + "", this.getTimeLineX(), Math.round(yPointer)); // increment hour++; yPointer += this.getPixelPerHour(); } } private void printWeekLine(Graphics2D g2d) { g2d.setColor(Color.BLACK); Calendar cPointer = (Calendar) this.calendarStart.clone(); int dayPointer = 0; int y = Math.round(this.getWeekLineHeight() * .8f); g2d.setColor(FamColors.FONT); while (dayPointer < 7) { String date = cPointer.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, FamRequestContainer.locale()) + " " + DateFormat.getDateInstance(DateFormat.SHORT, FamRequestContainer.locale()).format(cPointer.getTime()); g2d.drawString(date, this.getDayX(dayPointer) + PADDING_LEFT, y); cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } } private List<List<TimeFrame>> pTimeFramesOfWeek; private List<List<FacilityAvailability>> generalAvailabilitiesOfWeek; private void printDaysPanel(Graphics2D g2d) { int dayPointer = 0; while (dayPointer < 7) { List<TimeFrame> pTimeFrames = pTimeFramesOfWeek.get(dayPointer); Color[] colors = null; int dayX = this.getDayX(dayPointer); for (TimeFrame pTimeFrame : pTimeFrames) { colors = this.getColorsFor(pTimeFrame, this.bookingsOfWeek.get(dayPointer), generalAvailabilitiesOfWeek.get(dayPointer)); g2d.setColor(colors[1]); g2d.fillRect(dayX + stripeWidth, this.getY(pTimeFrame.getCalendarStart()), panelWidth, pTimeFrameHeight + 2); } dayPointer++; } } private int getDayHeight() { return this.getTimeLineHeight(); } private int getDayWidth() { return this.getWeekLineWidth() / 7; } private int getDayY() { return this.getWeekLineHeight(); } private int getDayX(int no) { return (no * this.getDayWidth()) + this.getTimeLineWidth(); } private int getY(Booking booking) { return Math.round(this.getY(booking.getSessionTimeFrame().getCalendarStart())); } private int getHeightOf(TimeFrame timeFrame) { return Math.round(this.getPixelPerHour() * (timeFrame.getDuration() / 3600000f)); // pixel // per // hour // * // duration // in // hours } private int getY(Calendar calendar) { Calendar dayStart = SimpleTimeFrame.getDay(calendar).getCalendarStart(); dayStart.add(Calendar.HOUR_OF_DAY, this.hourStart); int offset = this.getDayY(); if (calendar.before(dayStart)) { return offset; } else { return Math.round(offset + this.getHeightOf(new SimpleTimeFrame(dayStart, calendar))); } } private Color[] getColorsFor(TimeFrame pTimeFrame, List<Booking> bookingsOfDay, List<FacilityAvailability> generalAvailabilitiesOfDay) { Color stripe = null; Color panel = null; boolean colorFound = false; for (FacilityAvailability da : generalAvailabilitiesOfDay) { if (da.overlaps(pTimeFrame) && !da.isCompletelyAvailable()) { if (da.isNotAvailableBecauseOfSuddenFailure()) { stripe = FamColors.BLUE; panel = FamColors.BLUE_BRIGHT; } else if (da.isNotAvailableBecauseOfMaintenance()) { stripe = FamColors.GRAY; panel = FamColors.GRAY_BRIGHT; } else if (da.mustNotStartHere()) { stripe = FamColors.MUST_NOT_START_HERE; panel = FamColors.MUST_NOT_START_HERE_BRIGHT; } else { // opening hours stripe = FamColors.GRAY_BRIGHT; panel = FamColors.GRAY_BRIGHTER; } colorFound = true; } } if (!colorFound && bookingsOfDay != null) { // bookable facility and no // maintenance // count statuses int capacitySum = 0; for (Booking b : bookingsOfDay) { if (b.overlaps(pTimeFrame)) { capacitySum += b.getCapacityUnits(); } } if (capacitySum > 0) { if (((FacilityBookable) this.facility).getCapacityUnits() > capacitySum) { // NOT // completely // booked // up stripe = FamColors.PARTLY; panel = FamColors.PARTLY_BRIGHT; } else { // mainteneance or opening hours stripe = FamColors.FULL; panel = FamColors.FULL_BRIGHT; } colorFound = true; } } Color[] result = new Color[2]; result[0] = colorFound ? stripe : FamColors.FREE; result[1] = colorFound ? panel : FamColors.FREE_BRIGHT; return result; } private void printTopBlackLine(Graphics2D g2d) { g2d.setColor(Color.BLACK); int y1y2 = this.getDayY(); int x2 = this.getDayWidth() * 7 + this.getTimeLineWidth(); g2d.drawLine(this.getTimeLineX(), y1y2, x2, y1y2); } private void printHourLinesHorizontal(Graphics2D g2d) { int y1y2 = this.getDayY(); int x2 = this.getDayWidth() * 7 + this.getTimeLineWidth(); float opacity = 0.8f; g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); g2d.setColor(Color.WHITE); int x1 = this.getDayX(0); int stop = this.hourStop; int pointer = this.hourStart + 1; y1y2 += this.getPixelPerHour(); while (pointer < stop) { g2d.drawLine(x1, y1y2, x2, y1y2); pointer++; y1y2 += this.getPixelPerHour(); } g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); } private void printInfo(Graphics2D g2d) { float opacity = 0.3f; Font f = FamFonts.getFont(Font.BOLD, 20); int x = this.getImageWidth() - 5; int y = this.getImageHeight() - 10; AffineTransform trans90 = new AffineTransform(); trans90.setToRotation(-Math.PI / 2, x, y); g2d.setTransform(trans90); g2d.setFont(f); g2d.setColor(FamColors.FONT); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); g2d.drawString(this.facility.getLabel(), x, y); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); } private int stripeWidth = -1; private int panelWidth = -1; private int pTimeFrameHeight = -1; /** * this is invoked by the constructor before any other method */ private void setOptions() { Calendar cPointer = (Calendar) this.calendarStart.clone(); stripeWidth = (int) (this.getDayWidth() * .1); if (stripeWidth > 30) { stripeWidth = 30; } else if (stripeWidth < 5) { stripeWidth = 5; } panelWidth = this.getDayWidth() - stripeWidth; // create set bookable facility FacilityBookable bd = null; if (this.facility.isBookable()) { bd = (FacilityBookable) this.facility; } // set bookings this.bookingsOfWeek = new HashMap<Integer, List<Booking>>(); int dayPointer = 0; while (dayPointer < 7) { if (this.facility.isBookable()) { this.bookingsOfWeek.put(dayPointer, FamDaoProxy.bookingDao().getUncanceledBookingsWithoutApplicationsIn(bd, SimpleTimeFrame.getDay(cPointer))); } else { this.bookingsOfWeek.put(dayPointer, new ArrayList<Booking>()); } cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } cPointer.add(Calendar.DAY_OF_YEAR, -7); // set generalavailability of week this.generalAvailabilitiesOfWeek = new ArrayList<List<FacilityAvailability>>(); dayPointer = 0; while (dayPointer < 7) { TimeFrame entireDay = SimpleTimeFrame.getDay(cPointer); List<FacilityAvailability> tmpDas = FamDaoProxy.facilityDao().getFacilityAvailabilitiesMergedByFacilities(entireDay, this.facility.getKey()); if (tmpDas == null) { this.generalAvailabilitiesOfWeek.add(new ArrayList<FacilityAvailability>()); } else { this.generalAvailabilitiesOfWeek.add(tmpDas); } cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } cPointer.add(Calendar.DAY_OF_YEAR, -7); // set p time frames dayPointer = 0; TimeFrame pTimeFrame = SimpleTimeFrame.getDay(cPointer); Calendar pCalStart = pTimeFrame.getCalendarStart(); pCalStart.set(Calendar.HOUR_OF_DAY, this.hourStart); Calendar pCalEnd = (Calendar) pCalStart.clone(); pCalEnd.add(Calendar.MINUTE, FamCalendarConfiguration.smallestMinuteStep()); pTimeFrame = new SimpleTimeFrame(pCalStart, pCalEnd); pTimeFrameHeight = this.getHeightOf(pTimeFrame); this.pTimeFramesOfWeek = new ArrayList<List<TimeFrame>>(); while (dayPointer < 7) { List<TimeFrame> tmp = new ArrayList<TimeFrame>(); while (pTimeFrame.getCalendarStart().get(Calendar.DAY_OF_YEAR) == cPointer.get(Calendar.DAY_OF_YEAR)) { tmp.add(pTimeFrame.clone()); pTimeFrame.add(Calendar.MINUTE, FamCalendarConfiguration.smallestMinuteStep()); } this.pTimeFramesOfWeek.add(tmp); pTimeFrame.add(Calendar.HOUR_OF_DAY, this.hourStart); cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } cPointer.add(Calendar.DAY_OF_YEAR, -7); // set bookings for labels dayPointer = 0; this.bookinglabelsOfWeek = new HashMap<Integer, Map<Integer, List<Booking>>>(); while (dayPointer < 7) { Map<Integer, List<Booking>> labels = new HashMap<Integer, List<Booking>>(); // Map<Y // Position // in // Day, // booking> // collect labels of booking if (this.facility.isBookable()) { for (Booking booking : this.bookingsOfWeek.get(dayPointer)) { int bookingY = this.getDayY(); // assuming starting on // beginning of day here! if (booking.getSessionTimeFrame().getCalendarStart().get(Calendar.DAY_OF_YEAR) == cPointer.get(Calendar.DAY_OF_YEAR)) { // booking // starts // on // this // day bookingY = this.getY(booking); } if (!labels.containsKey(bookingY)) { labels.put(bookingY, new ArrayList<Booking>()); } labels.get(bookingY).add(booking); } } this.bookinglabelsOfWeek.put(dayPointer, labels); cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } cPointer.add(Calendar.DAY_OF_YEAR, -7); // set maintencane for labels dayPointer = 0; this.dasOfWeek = new HashMap<Integer, Map<Integer, FacilityAvailability>>(); while (dayPointer < 7) { // collect labels of availablities Map<Integer, FacilityAvailability> dasOfDay = new HashMap<Integer, FacilityAvailability>(); for (FacilityAvailability da : this.generalAvailabilitiesOfWeek.get(dayPointer)) { if (!da.isCompletelyAvailable() && da.getBasePeriodOfTime().getCalendarStart().get(Calendar.DAY_OF_YEAR) == cPointer.get(Calendar.DAY_OF_YEAR)) { // booking // starts // on // this // day int daY = this.getY(da); dasOfDay.put(daY, da); } } this.dasOfWeek.put(dayPointer, dasOfDay); cPointer.add(Calendar.DAY_OF_YEAR, 1); dayPointer++; } cPointer.add(Calendar.DAY_OF_YEAR, -7); this.textSplitter = new TextSplitterOnWidth(this.panelWidth - (2 * PADDING_LEFT), ' ', new StringMetricsGraphics(this.getFontForText())); } private Map<Integer, List<Booking>> bookingsOfWeek; private TextCol getTextCol(Font fontToUse) { TextCol result = new TextCol(); // create text areas int dayPointer = 0; int lineHeight = FamFonts.getLineHeight(fontToUse); while (dayPointer < 7) { int posX = this.getPrintOutPosX(dayPointer); // add text area for facility availability Map<Integer, FacilityAvailability> dasOfDay = this.dasOfWeek.get(dayPointer); for (Integer posY : dasOfDay.keySet()) { String tmp = ""; FacilityAvailability da = dasOfDay.get(posY); if (dasOfDay.get(posY).isNotAvailableInGeneral()) { tmp = "Not opened"; // INTLANG } else if (dasOfDay.get(posY).isNotAvailableBecauseOfSuddenFailure()) { tmp = "Failure"; // INTLANG } else if (dasOfDay.get(posY).mustNotStartHere()) { tmp = "Access denial"; // INTLANG } else { // maintenance tmp = "Maintenance"; // INTLANG } List<String> notices = this.getNotice(da.getNotice()); if (notices != null) { for (String notice : notices) { tmp += ": " + notice; } } TextArea ta = new TextArea(posX, posY, this.textSplitter, lineHeight); ta.addText(tmp); result.add(ta); } // add text areas for bookings Map<Integer, List<Booking>> bookings = this.bookinglabelsOfWeek.get(dayPointer); List<Integer> posYs = new ArrayList<Integer>(bookings.keySet()); for (Integer posY : posYs) { List<Booking> bookingsOnPos = bookings.get(posY); TextArea ta = new TextArea(posX, posY, this.textSplitter, lineHeight); // create the text if (bookingsOnPos.size() == 1) { // one booking on pos // print name String tmp = bookingsOnPos.get(0).getUser().getFullName(); if (!this.matchesPanelWidth(tmp)) { tmp = tmp.split(" ")[0] + " " + tmp.split(" ")[2]; if (!this.matchesPanelWidth(tmp)) { tmp = bookingsOnPos.get(0).getUser().getUsername(); if (!this.matchesPanelWidth(tmp)) { tmp = bookingsOnPos.get(0).getUser().getInitialing(); } } } ta.addText(tmp); } else { // more than one booking on pos // print how many people booked home many units int units = 0; for (Booking tmp : bookingsOnPos) { units += tmp.getCapacityUnits(); } ta.addText(String.format("%s bookings / %s units", bookingsOnPos.size(), units)); // INTLANG } result.add(ta); } // increment dayPointer++; } return result; } private void printDaysText(Graphics2D g2d) { TextCol textCol = this.getTextCol(g2d.getFont()); g2d.setColor(FamColors.FONT); int dayPointer = 0; int lineHeight = FamFonts.getLineHeight(g2d.getFont()); while (dayPointer < 7) { int posX = this.getPrintOutPosX(dayPointer); TextCol textAreasOfDay = textCol.getPosX(posX).doNotOverlap_moveDown(); for (TextArea ta : textAreasOfDay) { int posY = ta.getPosY() + lineHeight; for (String line : ta.getTextSplitted()) { g2d.drawString(line, ta.getPosX(), posY); posY += lineHeight; } } dayPointer++; } } private int PADDING_LEFT = 3; private TextSplitter textSplitter = null; /** * return true, if given string is smaller then panel width * * @param raw * @return */ private boolean matchesPanelWidth(String raw) { return FamFonts.getStringMetrics().getWidth(raw) <= this.panelWidth; } private List<String> getNotice(String raw) { if (raw == null) { return null; } else { return this.textSplitter.split(raw.replaceAll("\n", " ")); } } private void printDaysStripe(Graphics2D g2d) { int dayPointer = 0; while (dayPointer < 7) { List<TimeFrame> pTimeFrames = pTimeFramesOfWeek.get(dayPointer); Color[] colors = null; int dayX = this.getDayX(dayPointer); for (TimeFrame pTimeFrame : pTimeFrames) { int posY = this.getY(pTimeFrame.getCalendarStart()); colors = this.getColorsFor(pTimeFrame, this.bookingsOfWeek.get(dayPointer), generalAvailabilitiesOfWeek.get(dayPointer)); g2d.setColor(colors[0]); g2d.fillRect(dayX, posY, stripeWidth, pTimeFrameHeight + 2); } dayPointer++; } } private void printLinesVertical(Graphics2D g2d) { int dayPointer = 0; g2d.setColor(FamColors.FONT); while (dayPointer < 7) { g2d.drawLine(this.getDayX(dayPointer), 0, this.getDayX(dayPointer), this.getImageHeight()); dayPointer++; } } private int getY(FacilityAvailability da) { return Math.round(this.getY(da.getBasePeriodOfTime().getCalendarStart())); } private int getPrintOutPosX(int dayPointer) { return this.getDayX(dayPointer) + stripeWidth + PADDING_LEFT; } }