001: package com.technoetic.xplanner.export;
002:
003: import com.tapsterrock.mpx.MPXDuration;
004: import com.tapsterrock.mpx.MPXException;
005: import com.tapsterrock.mpx.MPXFile;
006: import com.tapsterrock.mpx.ProjectHeader;
007: import com.tapsterrock.mpx.Resource;
008: import com.tapsterrock.mpx.Task;
009: import com.tapsterrock.mpx.TimeUnit;
010: import com.technoetic.xplanner.domain.Iteration;
011: import com.technoetic.xplanner.domain.Person;
012: import com.technoetic.xplanner.domain.Project;
013: import com.technoetic.xplanner.domain.TimeEntry;
014: import com.technoetic.xplanner.domain.UserStory;
015: import net.sf.hibernate.Session;
016: import org.apache.commons.lang.StringUtils;
017:
018: import java.io.ByteArrayOutputStream;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Comparator;
023: import java.util.Date;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import javax.servlet.http.HttpServletResponse;
028:
029: public class MpxExporter implements Exporter {
030: public void initializeHeaders(HttpServletResponse response) {
031: response.setHeader("Content-type", "application/mpx");
032: response.setHeader("Content-disposition",
033: "inline; filename=export.mpx");
034: }
035:
036: public byte[] export(Session session, Object object)
037: throws ExportException {
038: try {
039: MPXFile file = new MPXFile();
040: file.setAutoTaskID(true);
041: file.setAutoTaskUniqueID(true);
042: file.setAutoResourceID(true);
043: file.setAutoResourceUniqueID(true);
044: file.setAutoOutlineLevel(true);
045: file.setAutoOutlineNumber(true);
046: file.setAutoWBS(true);
047: // Add a default calendar called "Standard"
048: // file.addDefaultBaseCalendar();
049:
050: ResourceRegistry resourceRegistry = new ResourceRegistry(
051: session
052: .find("from person in class "
053: + Person.class), file);
054:
055: if (object instanceof Project) {
056: exportProject(file, (Project) object, resourceRegistry);
057: } else if (object instanceof Iteration) {
058: exportIteration(file, null, (Iteration) object,
059: resourceRegistry);
060: }
061:
062: ByteArrayOutputStream data = new ByteArrayOutputStream();
063: file.write(data);
064: return data.toByteArray();
065: } catch (Exception e) {
066: throw new ExportException("exception during export", e);
067: }
068: }
069:
070: private String filterString(String s) {
071: return s.replaceAll("\r", "");
072: }
073:
074: private void exportProject(MPXFile file, Project project,
075: ResourceRegistry resources) throws MPXException {
076: ProjectHeader header = file.getProjectHeader();
077: Task projectLevelTask = file.addTask();
078: header.setProjectTitle(project.getName());
079: projectLevelTask.setName(project.getName());
080: if (!StringUtils.isEmpty(project.getDescription())) {
081: projectLevelTask.setNotes(filterString(project
082: .getDescription()));
083: }
084:
085: long earliestStartTime = Long.MAX_VALUE;
086: List iterations = new ArrayList(project.getIterations());
087: Collections.sort(iterations, new Comparator() {
088: public int compare(Object o1, Object o2) {
089: Iteration i1 = (Iteration) o1;
090: Iteration i2 = (Iteration) o2;
091: return i1.getStartDate().compareTo(i2.getStartDate());
092: }
093: });
094: for (Iterator iterator = iterations.iterator(); iterator
095: .hasNext();) {
096: Iteration iteration = (Iteration) iterator.next();
097: exportIteration(file, projectLevelTask, iteration,
098: resources);
099: if (iteration.getStartDate().getTime() < earliestStartTime) {
100: earliestStartTime = iteration.getStartDate().getTime();
101: }
102: }
103:
104: if (earliestStartTime < Long.MAX_VALUE) {
105: header.setStartDate(new Date(earliestStartTime));
106: } else {
107: header.setStartDate(new Date());
108: }
109: }
110:
111: private Task exportIteration(MPXFile file, Task projectLevelTask,
112: Iteration iteration, ResourceRegistry resources)
113: throws MPXException {
114: Task iterationLevelTask = null;
115: if (projectLevelTask != null) {
116: iterationLevelTask = projectLevelTask.addTask();
117: } else {
118: iterationLevelTask = file.addTask();
119: file.getProjectHeader().setStartDate(
120: iteration.getStartDate());
121: file.getProjectHeader()
122: .setProjectTitle(iteration.getName());
123: }
124: iterationLevelTask.setName(iteration.getName());
125: iterationLevelTask.setStart(iteration.getStartDate());
126: iterationLevelTask.setFinish(iteration.getEndDate());
127: if (StringUtils.isNotEmpty(iteration.getDescription())) {
128: iterationLevelTask.setNotes(filterString(iteration
129: .getDescription()));
130: }
131: for (Iterator iterator = iteration.getUserStories().iterator(); iterator
132: .hasNext();) {
133: UserStory userStory = (UserStory) iterator.next();
134: exportUserStory(iterationLevelTask, userStory, resources);
135: }
136: return iterationLevelTask;
137: }
138:
139: protected void exportUserStory(Task iterationLevelTask,
140: UserStory userStory, ResourceRegistry resources)
141: throws MPXException {
142: Task storyLevelTask = iterationLevelTask.addTask();
143: storyLevelTask.setName(userStory.getName());
144: if (StringUtils.isNotEmpty(userStory.getDescription())) {
145: storyLevelTask.setNotes(filterString(userStory
146: .getDescription()));
147: }
148: long earliestTaskStartTime = Long.MAX_VALUE;
149: Collection storyTasks = userStory.getTasks();
150: if (storyTasks.size() > 0) {
151: storyLevelTask.setWork(new MPXDuration(userStory
152: .getTaskBasedEstimatedHours(), TimeUnit.HOURS));
153: for (Iterator iterator = storyTasks.iterator(); iterator
154: .hasNext();) {
155: com.technoetic.xplanner.domain.Task task = (com.technoetic.xplanner.domain.Task) iterator
156: .next();
157: Task taskLevelTask = storyLevelTask.addTask();
158: taskLevelTask.setName(task.getName());
159: taskLevelTask.setDuration(new MPXDuration(task
160: .getEstimatedHours(), TimeUnit.HOURS));
161: if (StringUtils.isNotEmpty(task.getDescription())) {
162: taskLevelTask.setNotes(filterString(task
163: .getDescription()));
164: }
165: if (task.getAcceptorId() != 0) {
166: taskLevelTask.addResourceAssignment(resources
167: .getResource(task.getAcceptorId()));
168: } else {
169: taskLevelTask.setWork(new MPXDuration(task
170: .getEstimatedHours(), TimeUnit.HOURS));
171: }
172: if (task.getActualHours() != 0) {
173: taskLevelTask.setActualWork(new MPXDuration(task
174: .getActualHours(), TimeUnit.HOURS));
175: Date startTime = getStartTime(iterationLevelTask,
176: task);
177: if (startTime != null) {
178: taskLevelTask.setActualStart(startTime);
179: }
180: }
181: if (task.getTimeEntries().size() > 0) {
182: earliestTaskStartTime = exportTimeEntries(task,
183: taskLevelTask, earliestTaskStartTime);
184: } else {
185: taskLevelTask.setActualStart(iterationLevelTask
186: .getActualStart());
187: }
188: }
189: if (earliestTaskStartTime == Long.MAX_VALUE) {
190: earliestTaskStartTime = iterationLevelTask.getStart()
191: .getTime();
192: }
193: List tasks = storyLevelTask.getChildTasks();
194: for (int i = 0; i < tasks.size(); i++) {
195: Task task = (Task) tasks.get(i);
196: if (task.getActualStart() == null) {
197: task
198: .setActualStart(new Date(
199: earliestTaskStartTime));
200: }
201: }
202: } else {
203: storyLevelTask
204: .setActualStart(iterationLevelTask.getStart());
205: storyLevelTask.setDuration(new MPXDuration(userStory
206: .getEstimatedHours(), TimeUnit.HOURS));
207: storyLevelTask.setWork(new MPXDuration(userStory
208: .getEstimatedHours(), TimeUnit.HOURS));
209: int trackerId = userStory.getTrackerId();
210: if (trackerId != 0) {
211: storyLevelTask.addResourceAssignment(resources
212: .getResource(trackerId));
213: }
214: }
215: }
216:
217: private long exportTimeEntries(
218: com.technoetic.xplanner.domain.Task task,
219: Task taskLevelTask, long earliestTaskStartTime) {
220: long startTime = Long.MAX_VALUE;
221: for (Iterator timeItr = task.getTimeEntries().iterator(); timeItr
222: .hasNext();) {
223: TimeEntry timeEntry = (TimeEntry) timeItr.next();
224: if (timeEntry.getStartTime() != null
225: && timeEntry.getStartTime().getTime() < startTime) {
226: startTime = timeEntry.getStartTime().getTime();
227: }
228: }
229: if (startTime < Long.MAX_VALUE) {
230: taskLevelTask.setActualStart(new Date(startTime));
231: if (startTime < earliestTaskStartTime) {
232: earliestTaskStartTime = startTime;
233: }
234: }
235: return earliestTaskStartTime;
236: }
237:
238: private Date getStartTime(Task iterationLevelTask,
239: com.technoetic.xplanner.domain.Task task) {
240: Date start = null;
241: for (Iterator iterator = task.getTimeEntries().iterator(); iterator
242: .hasNext();) {
243: TimeEntry timeEntry = ((TimeEntry) iterator.next());
244: Date timeEntryStart = timeEntry.getStartTime();
245: if (timeEntryStart == null) {
246: timeEntryStart = timeEntry.getReportDate();
247: long durationMs = (long) timeEntry.getDuration() * 3600000L;
248: long timeEntryEnd = timeEntryStart.getTime()
249: + durationMs;
250: if (timeEntryEnd > iterationLevelTask.getFinish()
251: .getTime()) {
252: timeEntryStart = new Date(iterationLevelTask
253: .getFinish().getTime()
254: - durationMs);
255: }
256: }
257: if (start == null
258: || (timeEntryStart != null && timeEntryStart
259: .getTime() < start.getTime())) {
260: start = timeEntryStart;
261: }
262: }
263: return start;
264: }
265:
266: protected static class ResourceRegistry {
267: private HashMap resources = new HashMap();
268:
269: public ResourceRegistry(List people, MPXFile mpxFile)
270: throws MPXException {
271: try {
272: for (int i = 0; i < people.size(); i++) {
273: Person person = (Person) people.get(i);
274: Resource resource = mpxFile.addResource();
275: resource.setName(person.getName());
276: resources
277: .put(new Integer(person.getId()), resource);
278: }
279: } catch (RuntimeException ex) {
280: throw ex;
281: } catch (Exception e) {
282: throw new MPXException(
283: "error loading people for MPX export", e);
284: }
285: }
286:
287: public Resource getResource(int i) {
288: return (Resource) resources.get(new Integer(i));
289: }
290: }
291: }
|