001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.pm.task;
051:
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.HashSet;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.Set;
058:
059: import javax.swing.JOptionPane;
060:
061: import org.apache.commons.collections.Closure;
062:
063: import com.projity.grouping.core.Node;
064: import com.projity.grouping.core.model.AssignmentNodeModel;
065: import com.projity.grouping.core.model.DefaultNodeModel;
066: import com.projity.grouping.core.model.NodeModel;
067: import com.projity.grouping.core.model.NodeModelUtil;
068: import com.projity.grouping.core.summaries.DeepChildWalker;
069: import com.projity.job.Job;
070: import com.projity.job.JobRunnable;
071: import com.projity.pm.resource.ResourcePool;
072: import com.projity.pm.resource.ResourcePoolFactory;
073: import com.projity.server.data.DataUtil;
074: import com.projity.session.CreateOptions;
075: import com.projity.session.LoadOptions;
076: import com.projity.session.LocalSession;
077: import com.projity.session.SaveOptions;
078: import com.projity.session.Session;
079: import com.projity.session.SessionFactory;
080: import com.projity.strings.Messages;
081: import com.projity.undo.DataFactoryUndoController;
082: import com.projity.util.Alert;
083: import com.projity.util.Environment;
084:
085: /**
086: *
087: */
088: public class ProjectFactory {
089: private static int untitledCount = 0;
090: private String server = null;
091: Portfolio portfolio; // for now just one portfolio. Perhaps portfolio should reference project factory and not like this
092: private static ProjectFactory projectFactory;
093:
094: public static ProjectFactory getInstance() {
095: if (projectFactory == null)
096: projectFactory = new ProjectFactory();
097: return projectFactory;
098: }
099:
100: public static ProjectFactory createInstance() {
101: return new ProjectFactory();
102: }
103:
104: private ProjectFactory() {
105: portfolio = new Portfolio(this );
106: }
107:
108: //CREATE PROJECTS
109:
110: // public Project createProject(String name,boolean local) {
111: // return createProject(name,local,true,true);
112: // }
113: // public Project createProject(String name, boolean local, boolean addResources,boolean verify) {
114: // Project project = createProject(null,local,name,addResources,verify);
115: // return project;
116: // }
117: //
118: public Project createProject() {
119: CreateOptions opt = new CreateOptions();
120: opt.setLocal(Environment.getStandAlone());
121: opt.setName(Messages.getString("Text.Untitled") + " "
122: + ++untitledCount);
123: return createProject(opt);
124: }
125:
126: // public Project createProject(boolean addResources,boolean local) {
127: // Project project = createProject(Messages.getString("Text.Untitled") + " " + ++untitledCount,local,addResources,true);
128: // return project;
129: // }
130:
131: // public Project createProject(ResourcePool resourcePool, boolean local, String name) {
132: // return createProject(resourcePool,local,name,!local,true);
133: // }
134: // public Project createProject(ResourcePool resourcePool, boolean local, String name, boolean addResources,boolean verify) {
135: private Project createProjectAsync(CreateOptions opt) {
136: DataFactoryUndoController undoController = new DataFactoryUndoController();
137: ResourcePool resourcePool = opt.getResourcePool();
138: if (resourcePool == null) {
139: resourcePool = ResourcePoolFactory.getInstance()
140: .createResourcePool(opt.getName(), undoController);
141: resourcePool.setLocal(opt.isLocal());
142: }
143: Project project = Project.createProject(resourcePool,
144: undoController);
145: undoController.setDataFactory(project);
146: project.setName(opt.getName());
147: if (opt.isLocal())
148: project.setMaster(true);
149:
150: //Don't forget to modify Serializer.deserialize too
151: if (opt.isAddResources() && !project.isLocal()) {
152: try {
153: Session session = SessionFactory.getInstance()
154: .getSession(false);
155: List resources;
156: if (((Boolean) SessionFactory.callNoEx(session,
157: "isLocalAccess", null, null)).booleanValue())
158: resources = (List) SessionFactory.call(session,
159: "retrieveResourceHierarchy", null, null);
160: else {
161: resources = new ArrayList();
162:
163: Job job = (Job) SessionFactory.callNoEx(session,
164: "getLoadResourceHierarchyJob", new Class[] {
165: boolean.class, List.class },
166: new Object[] { true, resources });
167: job.addSync();
168: session.schedule(job);
169: //job.waitResult();
170: }
171: DataUtil
172: .setEnterpriseResources(resources, resourcePool);
173: } catch (Exception e) {
174: // TODO Auto-generated catch block
175: e.printStackTrace();
176: }
177: }
178:
179: project.setInitialized(true);
180: //two following lines inverted to have a NodeModel with dataFactory set in DocumentFrame
181: ((DefaultNodeModel) project.getTaskOutline())
182: .setDataFactory(project);
183: addProject(project, !opt.isSync(), opt.isVerify());
184: System.out.println("Project returned");
185: return project;
186: }
187:
188: public Project createProject(final CreateOptions opt) {
189: JobRunnable runnable = new JobRunnable("Local: create Project") {
190: public Object run() throws Exception {
191: return createProjectAsync(opt);
192: }
193: };
194: if (opt.isSync()) {
195: Job job = new Job(SessionFactory.getInstance()
196: .getJobQueue(), "createProject",
197: "Creating project...", false);
198: job.addRunnable(runnable);
199: job.addSync();
200: SessionFactory.getInstance().schedule(job);
201: try {
202: Project project = (Project) job.waitResult();
203: System.out.println("Project returned end lock");
204: return project;
205: } catch (Exception e) {//Forward exception + Alert
206: e.printStackTrace();
207: return null;
208: }
209:
210: } else {
211: try {
212: return (Project) runnable.run();
213: } catch (Exception e) {
214: e.printStackTrace();
215: return null;
216: }
217: }
218: }
219:
220: public void addProject(Project project, boolean verify) {
221: addProject(project, true, verify);
222: }
223:
224: public void addProject(Project project, boolean createJob,
225: boolean verify) {
226: portfolio.addProject(project, createJob, verify);
227: }
228:
229: // public Project openDownloadedProject() {
230: // return null;
231: // }
232:
233: protected Set loadingProjects = new HashSet();
234: protected Set closingProjects = new HashSet();
235:
236: public synchronized Set getOpenOrLoadingProjects() {
237: final Set projectIds = new HashSet();
238: ProjectFactory.getInstance().getPortfolio().forProjects(
239: new Closure() {
240: public void execute(Object impl) {
241: Project project = (Project) impl;
242: projectIds.add(new Long(project.getUniqueId()));
243: }
244: });
245: projectIds.addAll(loadingProjects);
246: projectIds.removeAll(closingProjects);
247: return projectIds;
248: }
249:
250: public synchronized void addLoadingProject(long id) {
251: loadingProjects.add(new Long(id));
252: }
253:
254: public synchronized void removeLoadingProject(long id) {
255: loadingProjects.remove(new Long(id));
256: }
257:
258: public synchronized void addClosingProjects(Collection ids) {
259: closingProjects.addAll(ids);
260: }
261:
262: public synchronized void addClosingProject(long id) {
263: closingProjects.add(new Long(id));
264: }
265:
266: public synchronized void removeClosingProject(long id) {
267: closingProjects.remove(new Long(id));
268: }
269:
270: //OPEN PROJECTS
271:
272: public Project openProject(final LoadOptions opt) {
273: Session session = SessionFactory.getInstance().getSession(
274: opt.isLocal());
275: Job job = null;
276: final boolean recover;
277: if (opt.getId() > 0) {
278: Project p = findFromId(opt.getId());
279: if (p != null && !opt.isOpenAs()) {
280: job = session.getEmptyJob("Recover project", p);
281: recover = true;
282: } else {
283: addLoadingProject(opt.getId());
284: recover = false;
285: }
286: } else
287: recover = false;
288:
289: if (job == null)
290: job = session.getLoadProjectJob(opt);
291: job.addSwingRunnable(new JobRunnable("Local: addProject") {
292: public Object run() throws Exception {
293: Project project = (Project) getPreviousResult();
294: if (!recover) {
295: if (project != null)
296: addProject(project, false, true);
297: if (opt.getId() > 0)
298: removeLoadingProject(opt.getId());
299: }
300: if (opt.getEndSwingClosure() != null)
301: opt.getEndSwingClosure().execute(project);
302: return project;
303: }
304: }, false);
305: if (opt.isSync())
306: job.addSync();
307: session.schedule(job);
308: try {
309: return (opt.isSync()) ? (Project) job.waitResult() : null;
310: } catch (Exception e) {//Forward exception + Alert
311: return null;
312: }
313: }
314:
315: public Project findFromId(long id) {
316: return portfolio.findByUniqueId(id);
317: }
318:
319: public Project openSubproject(final Project parent,
320: final Node subprojectNode, final boolean creating) {
321: final SubProj subprojectTask = (SubProj) subprojectNode
322: .getImpl();
323: final long id = subprojectTask.getSubprojectUniqueId();
324: Project openSubproject = portfolio.findByUniqueId(id);
325: if (openSubproject != null) {
326: parent.getSubprojectHandler().addSubproject(openSubproject,
327: subprojectNode, creating, true);
328: portfolio.handleExternalTasks(openSubproject, true, false); // resolve external links if any
329: return openSubproject;
330: }
331:
332: addLoadingProject(id);
333: final Session session = SessionFactory.getInstance()
334: .getSession(parent.isLocal());
335: LoadOptions opt = new LoadOptions();
336: opt.setSubproject(true);
337: opt.setId(id);
338: Job job = session.getLoadProjectJob(opt);
339: subprojectTask.setFetching(true);
340:
341: job.addSwingRunnable(new JobRunnable("Local: insertProject") {
342: public Object run() throws Exception {
343: try {
344: Project subproject = (Project) getPreviousResult();
345:
346: //add assignments in the outline, paste uses only assignments present in the nodeModel
347: AssignmentNodeModel parentModel = (AssignmentNodeModel) subproject
348: .getTaskOutline();
349: parentModel.addAssignments(parentModel.iterator()); // assignments
350:
351: if (subproject != null) {// is it possible it can be null?
352: parent.getSubprojectHandler().addSubproject(
353: subproject, subprojectNode, creating,
354: false);
355: //
356: // subproject.setGroupDirty(true);
357: // //TODO something more precise here
358: }
359: } catch (Exception e) {
360: // TODO Auto-generated catch block
361: e.printStackTrace();
362: throw e;
363: } finally {
364: subprojectTask.setFetching(false);
365: removeLoadingProject(id);
366:
367: }
368: return null; //return not used anyway
369: }
370: }, false);
371:
372: session.schedule(job);
373: return ((Task) subprojectTask).getProject();
374: }
375:
376: //SAVE PROJECTS
377:
378: public void saveProject(final Project project, final SaveOptions opt) {
379: Job job = getSaveProjectJob(project, opt);
380: Session session = SessionFactory.getInstance().getSession(
381: opt.isLocal());
382: if (job != null) {
383: if (opt.isSync())
384: job.addSync();
385: session.schedule(job);
386: try {
387: if (opt.isSync())
388: job.waitResult();
389: } catch (Exception e) {
390: }
391: }
392: }
393:
394: public Job getSaveProjectJob(final Project project,
395: final SaveOptions opt) {
396: // Save the project and all of its subprojects
397: final List projects = new ArrayList();
398: DeepChildWalker.recursivelyTreatBranch(
399: portfolio.getNodeModel(), project, new Closure() {
400: boolean dirty = false;
401:
402: public void execute(Object arg0) {
403: Node n = (Node) arg0;
404: Object impl = n.getImpl();
405: if (impl instanceof Project) {
406: Project p = (Project) impl;
407: if (((Node) n.getParent()).isRoot())
408: dirty = p.needsSaving(); //&& !p.isReadOnly();
409: if (dirty || opt.isSaveAs()
410: || opt.getImporter() != null) {
411: p
412: .setEarliestAndLatestDatesFromSchedule(); // we want subprojects to have their dates set by external constraints if any
413: projects.add(p);
414: }
415: }
416: }
417: });
418: if (projects.size() > 0) {
419: Session session = SessionFactory.getInstance().getSession(
420: opt.isLocal());
421: final SaveOptions o = (SaveOptions) opt.clone();
422: o.setPostSaving(new Closure() {
423: public void execute(Object obj) {
424: Project p = (Project) obj;
425: p.setAllTasksAsUnchangedFromPersisted();
426: portfolio.handleExternalTasks(p, false, true); // external link handling
427: if (opt.getPostSaving() != null)
428: opt.getPostSaving().execute(obj); //id, combobox update
429: }
430: });
431: Job job = session.getSaveProjectJob(projects, o);
432: return job;
433: }
434: return null;
435: }
436:
437: //CLOSE PROJECTS
438:
439: public Job getCloseProjectsOnServerJob(Project project) {
440: // Save the project and all of its subprojects
441: final List projects = new ArrayList();
442: DeepChildWalker.recursivelyTreatBranch(
443: portfolio.getNodeModel(), project, new Closure() {
444: public void execute(Object arg0) {
445: Object impl = ((Node) arg0).getImpl();
446: if (impl instanceof Project) {
447: projects.add(impl);
448: }
449: }
450: });
451: if (projects.size() > 0) {
452: Session session = SessionFactory.getInstance().getSession(
453: project.isLocal()); //assume same type for subprojets
454: Job job = session.getCloseProjectsJob(projects);
455: return job;
456: }
457: return null;
458: }
459:
460: public Job getCloseProjectsOnServerJob(Collection projects) {
461: List<Project> localProjects = new ArrayList<Project>();
462: List<Project> serverProjects = new ArrayList<Project>();
463: for (Project project : (Collection<Project>) projects) {
464: if (project.isLocal())
465: localProjects.add(project);
466: else
467: serverProjects.add(project);
468: }
469: Job job = null;
470: if (localProjects.size() > 0)
471: job = SessionFactory.getInstance().getLocalSession()
472: .getCloseProjectsJob(projects);
473: else if (serverProjects.size() > 0) {
474: Job j = SessionFactory.getInstance().getSession(false)
475: .getCloseProjectsJob(projects);
476: if (job == null)
477: job = j;
478: else
479: job.addJob(j);
480: }
481: return job;
482: }
483:
484: public int promptForSave(Project project, boolean allowCancel) {
485: String text = Messages
486: .getString("Message.saveProjectBeforeClosing1")
487: + " "
488: + project.getName()
489: + " "
490: + Messages
491: .getString("Message.saveProjectBeforeClosing2");
492: if (allowCancel)
493: return Alert.confirm(text);
494: else
495: return Alert.confirmYesNo(text);
496: }
497:
498: /**
499: * @param project
500: * @param allowCancel
501: * @param prompt
502: * @return null if cancelled
503: */
504: public Job getRemoveProjectJob(final Project project,
505: boolean allowCancel, boolean prompt) {
506: Job job = null;
507: if (prompt && project.needsSaving()) {
508: int promptResult = promptForSave(project, allowCancel);
509: if (promptResult == JOptionPane.YES_OPTION) {
510: SaveOptions opt = new SaveOptions();
511: opt.setLocal(project.isLocal());
512: if (project.isLocal()) {
513: String fileName = project.getFileName();
514: if (fileName == null) {
515: fileName = SessionFactory.getInstance()
516: .getLocalSession().chooseFileName(true,
517: project.getGuessedFileName());
518: }
519: if (fileName == null)
520: return null;
521: project.setFileName(fileName);
522: opt.setFileName(fileName);
523: opt.setImporter(LocalSession.getImporter(project
524: .getFileType()));
525: }
526: job = getSaveProjectJob(project, opt);
527: } else if (promptResult == JOptionPane.CANCEL_OPTION)
528: return null;
529: }
530:
531: final ArrayList toRemove = new ArrayList();
532: final ArrayList projects = new ArrayList();
533: DeepChildWalker.recursivelyTreatBranch(
534: portfolio.getNodeModel(), project, new Closure() {
535: public void execute(Object arg0) {
536: Node node = (Node) arg0;
537: Object impl = node.getImpl();
538: if (!(impl instanceof Project))
539: return;
540: final Project p = (Project) impl;
541: toRemove.add(node);
542: if (Environment.getStandAlone()
543: || project.isLockable()) {
544: projects.add(p);
545: }
546: }
547: });
548:
549: Job closeProjectJob = getCloseProjectsOnServerJob(projects);
550: if (closeProjectJob == null) {
551: closeProjectJob = new Job(SessionFactory.getInstance()
552: .getJobQueue(), "closeProjects", "Closing...",
553: false);
554:
555: }
556: if (job == null)
557: job = closeProjectJob;
558: else
559: job.addJob(closeProjectJob);
560:
561: job.addSwingRunnable(new JobRunnable("Local: closeProjects") {
562: public Object run() throws Exception {
563: Iterator i = toRemove.iterator();
564: while (i.hasNext()) {
565: Node node = (Node) i.next();
566: Project p = (Project) node.getImpl();
567: portfolio.handleExternalTasks(p, false, false); // external link handling
568: p.getResourcePool().removeProject(p);
569: p.disconnect();
570: portfolio.getObjectEventManager().fireDeleteEvent(
571: this , p);
572: portfolio.getNodeModel().remove(node,
573: NodeModel.EVENT);
574:
575: removeClosingProject(project.getUniqueId());
576: }
577: System.gc(); // clean up memory used by projects
578: return null; //return not used anyway
579: }
580: }, false);
581: return job;
582: }
583:
584: public void removeProject(final Project project,
585: boolean allowCancel, boolean prompt) {
586: Job job = getRemoveProjectJob(project, allowCancel, prompt);
587: if (job != null) { // if not cancelled
588: Session session = SessionFactory.getInstance().getSession(
589: project.isLocal());
590: session.schedule(job);
591: }
592: }
593:
594: public void doRemoveProject(Project project) {
595: Job job = projectFactory.getPortfolio().getRemoveProjectJob(
596: project);
597: if (job != null) {
598: SessionFactory.getInstance().getSession(project.isLocal())
599: .schedule(job);
600: portfolio.handleExternalTasks(project, false, false); // external link handling
601: }
602:
603: }
604:
605: /**
606: * @return Returns the portfolio.
607: */
608: public Portfolio getPortfolio() {
609: return portfolio;
610: }
611:
612: /**
613: * @return Returns the server.
614: */
615: public final String getServer() {
616: return server;
617: }
618:
619: /**
620: * @param server The server to set.
621: */
622: public final void setServer(String server) {
623: this .server = server;
624: }
625:
626: public Collection getDirtyProjectList() {
627: return portfolio.getDirtyProjectList();
628: }
629:
630: }
|