001: package com.technoetic.xplanner.ical;
002:
003: import com.technoetic.xplanner.db.hibernate.ThreadSession;
004: import com.technoetic.xplanner.domain.Task;
005: import com.technoetic.xplanner.format.PrintfFormat;
006: import com.technoetic.xplanner.security.AuthenticationException;
007: import com.technoetic.xplanner.security.PersonPrincipal;
008: import com.technoetic.xplanner.security.SecurityHelper;
009: import com.technoetic.xplanner.security.auth.SystemAuthorizer;
010: import net.sf.hibernate.Hibernate;
011: import net.sf.hibernate.HibernateException;
012: import net.sf.hibernate.Session;
013: import net.sf.hibernate.type.Type;
014: import org.apache.log4j.Logger;
015:
016: import javax.servlet.ServletException;
017: import javax.servlet.http.HttpServlet;
018: import javax.servlet.http.HttpServletRequest;
019: import javax.servlet.http.HttpServletResponse;
020: import java.io.IOException;
021: import java.io.PrintWriter;
022: import java.util.Calendar;
023: import java.util.Date;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.ResourceBundle;
027:
028: /**
029: * Original implementation contributed by Emiliano Heyns (emiliano@iris-advies.nl)
030: */
031: public class iCalServlet extends HttpServlet {
032: private Logger log = Logger.getLogger(getClass());
033: private PrintfFormat pfDate = new PrintfFormat(
034: "%04d%02d%02dT%02d%02d%02d");
035: private Calendar calendar = Calendar.getInstance();
036: private final static int ICAL_LINELENGTH_LIMIT = 75;
037:
038: public void doGet(HttpServletRequest req, HttpServletResponse res)
039: throws ServletException, IOException {
040: try {
041: int myID = 0;
042: String myUsername = null;
043:
044: PersonPrincipal me;
045: try {
046: me = (PersonPrincipal) (SecurityHelper
047: .getUserPrincipal(req));
048: } catch (AuthenticationException e) {
049: res.sendError(
050: HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e
051: .getMessage());
052: return;
053: }
054: myID = me.getPerson().getId();
055: myUsername = me.getPerson().getUserId();
056:
057: String requestedUsername = req.getPathInfo();
058: if (requestedUsername == null
059: || requestedUsername.equals("/")) {
060: res.sendError(HttpServletResponse.SC_BAD_REQUEST,
061: "No iCal file requested.");
062: return;
063: }
064:
065: if (requestedUsername.startsWith("/")) {
066: requestedUsername = requestedUsername.substring(1);
067: }
068:
069: if (!requestedUsername.endsWith(".ics")) {
070: res.sendError(HttpServletResponse.SC_BAD_REQUEST,
071: "No .ics suffix for requested iCal file.");
072: return;
073: }
074:
075: requestedUsername = requestedUsername.substring(0,
076: requestedUsername.length() - 4);
077:
078: Session session = ThreadSession.get();
079:
080: // verify access of http user vs requested user
081: if (!requestedUsername.equals(myUsername)) {
082: if (!SystemAuthorizer.get()
083: .hasPermissionForSomeProject(myID,
084: "system.person", myID, "admin.edit")) {
085: res.sendError(HttpServletResponse.SC_FORBIDDEN,
086: "No authorization for accessing calendar for "
087: + requestedUsername);
088: return;
089: }
090: }
091:
092: String taskURL = req.getScheme() + "://"
093: + req.getServerName() + ":" + req.getServerPort()
094: + req.getContextPath();
095: if (!taskURL.endsWith("/")) {
096: taskURL = taskURL + "/";
097: }
098: taskURL = taskURL + "do/view/task?oid=";
099: String guid = "-xplanner"
100: + req.getContextPath().replace('/', '_') + "@"
101: + req.getServerName();
102:
103: // response.setHeader("Content-type", "text/calendar");
104: // pacify outlook
105: res.setHeader("Content-type", "application/x-msoutlook");
106: res.setHeader("Content-disposition", "inline; filename="
107: + requestedUsername + ".ics");
108:
109: PrintWriter out = res.getWriter();
110:
111: out.write("BEGIN:VCALENDAR\r\n");
112: out.write("VERSION:1.0\r\n");
113: out.write("PRODID:-//Iris Advies//XPlanner//EN\r\n");
114:
115: generateTimeEntryData(out, session, requestedUsername,
116: guid, taskURL);
117: generateTaskData(out, session, requestedUsername, guid,
118: taskURL);
119:
120: out.write("END:VCALENDAR\r\n");
121:
122: } catch (Exception e) {
123: log.error("ical error", e);
124: res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
125: e.getMessage());
126: }
127: }
128:
129: private void generateTaskData(PrintWriter out,
130: Session hibernateSession, String requestedUsername,
131: String guid, String taskURL) throws HibernateException {
132:
133: String query = "select task, story.name, story.priority, iteration.name, project.name"
134: + " from com.technoetic.xplanner.domain.Task task,"
135: + " person in class com.technoetic.xplanner.domain.Person,"
136: + " story in class com.technoetic.xplanner.domain.UserStory,"
137: + " iteration in class com.technoetic.xplanner.domain.Iteration,"
138: + " project in class com.technoetic.xplanner.domain.Project"
139: + " where"
140: + " task.completed = false and task.type <> ?"
141: + " and person.userId = ? and (person.id = task.acceptorId)"
142: + " and task.story = story.id and story.iterationId = iteration.id"
143: + " and iteration.projectId = project.id";
144:
145: String overhead = null;
146: try {
147: ResourceBundle resources = ResourceBundle
148: .getBundle("ResourceBundle");
149: overhead = resources.getString("task.type.overhead");
150: } catch (Exception e) {
151: overhead = null;
152: }
153:
154: // allow for people to not have overhead tasks
155: if (overhead == null) {
156: overhead = "";
157: }
158:
159: List tasks = hibernateSession.find(query, new Object[] {
160: overhead, requestedUsername }, new Type[] {
161: Hibernate.STRING, Hibernate.STRING });
162: Iterator iter = tasks.iterator();
163: while (iter.hasNext()) {
164: Object[] result = (Object[]) iter.next();
165: Task task = (Task) result[0];
166:
167: out.write("BEGIN:VTODO\r\n");
168: out.write("UID:task-" + task.getId() + guid + "\r\n");
169: out.write(quote("SUMMARY:" + task.getName()) + "\r\n");
170: // SunBird doesn't support multi-value categories
171: out.write(quote("CATEGORIES:" + result[4] + "\n"
172: + result[3] + "\n" + result[1])
173: + "\r\n");
174: out.write("PERCENT-COMPLETE:"
175: + ((int) ((task.getActualHours() * 100) / (task
176: .getActualHours() + task
177: .getRemainingHours()))) + "\r\n");
178: out.write("PRIORITY:" + result[2] + "\r\n");
179: out.write("STATUS:IN-PROCESS\r\n");
180: out.write(quote("URL:" + taskURL + task.getId()) + "\r\n");
181: out.write("END:VTODO\r\n");
182: }
183: }
184:
185: private void generateTimeEntryData(PrintWriter out,
186: Session hibernateSession, String requestedUsername,
187: String guid, String taskURL) throws HibernateException {
188:
189: String query = "select entry.id, entry.startTime, entry.endTime, entry.duration,"
190: + " task.id, task.name,"
191: + " story.name"
192: + " from"
193: + " entry in class com.technoetic.xplanner.domain.TimeEntry,"
194: + " person in class com.technoetic.xplanner.domain.Person,"
195: + " task in class com.technoetic.xplanner.domain.Task,"
196: + " story in class com.technoetic.xplanner.domain.UserStory"
197: + " where"
198: + " person.userId = ? and (person.id = entry.person1Id or person.id = entry.person2Id)"
199: + " and entry.taskId = task.id"
200: + " and task.story = story.id";
201:
202: List events = hibernateSession.find(query, requestedUsername,
203: Hibernate.STRING);
204: Iterator iter = events.iterator();
205: while (iter.hasNext()) {
206: Object[] result = (Object[]) iter.next();
207:
208: if (result[1] != null && result[3] != null) {
209: out.write("BEGIN:VEVENT\r\n");
210: out.write("UID:timeentry-" + result[0] + guid + "\r\n");
211: out.write(quote("SUMMARY:" + result[5] + "\n"
212: + result[6])
213: + "\r\n");
214: out.write("DTSTART:" + formatDate((Date) result[1])
215: + "\r\n");
216: // do-before-release review how to handle null end times on time entries
217: if (result[2] != null) {
218: out.write("DTEND:" + formatDate((Date) result[2])
219: + "\r\n");
220: } else {
221: out.write("DTEND:" + formatDate((Date) result[1])
222: + "\r\n");
223: }
224: out.write(quote("URL:" + taskURL + result[4]) + "\r\n");
225: out.write("END:VEVENT\r\n");
226: }
227: }
228: }
229:
230: private String formatDate(Date d) {
231: calendar.setTime(d);
232: return pfDate.sprintf(new Object[] {
233: new Integer(calendar.get(Calendar.YEAR)),
234: new Integer(calendar.get(Calendar.MONTH) + 1),
235: new Integer(calendar.get(Calendar.DAY_OF_MONTH)),
236: new Integer(calendar.get(Calendar.HOUR_OF_DAY)),
237: new Integer(calendar.get(Calendar.MINUTE)),
238: new Integer(calendar.get(Calendar.SECOND)) });
239: }
240:
241: private String quote(String s) {
242: char[] chars = s.toCharArray();
243: int length = s.length();
244: // initialize to 1 to account for the first quoted char
245: int linelength = 1;
246: StringBuffer sb = new StringBuffer(length);
247:
248: for (int i = 0; i < length; i++) {
249: if (linelength >= ICAL_LINELENGTH_LIMIT) {
250: sb.append("\r\n ");
251: // initialize to 1 to account for the first quoted char
252: linelength = 1;
253: }
254:
255: switch (chars[i]) {
256: case ',':
257: sb.append("\\,");
258: linelength += 2;
259: break;
260: case ';':
261: sb.append("\\;");
262: linelength += 2;
263: break;
264: case '\n':
265: // SunBird doesn't support multi-line texts
266: // sb.append("\\n"); linelength += 2;
267: sb.append(" :: ");
268: linelength += 4;
269: break;
270: default:
271: sb.append(chars[i]);
272: linelength++;
273: break;
274: }
275: }
276:
277: return sb.toString();
278: }
279: }
|