4 * @author Daniel Brunier-Coulin
5 * @copyright OPEN CASCADE 2012
8 import java.text.DecimalFormat;
9 import java.text.SimpleDateFormat;
10 import java.util.Arrays;
11 import java.util.Calendar;
12 import java.util.Date;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Vector;
17 import org.hibernate.Hibernate;
18 import org.hibernate.Session;
20 import org.splat.kernel.NotApplicableException;
21 import org.splat.kernel.Persistent;
22 import org.splat.kernel.Relation;
23 import org.splat.kernel.InvalidPropertyException;
24 import org.splat.kernel.MissedPropertyException;
25 import org.splat.kernel.MultiplyDefinedException;
26 import org.splat.kernel.User;
27 import org.splat.manox.Reader;
28 import org.splat.manox.Toolbox;
29 import org.splat.som.ProjectSettings.FileNaming;
30 import org.splat.som.Timestamp.ComparatorByDate;
33 public class Document extends Entity {
36 private DocumentType type; // User expendable types
40 private ProgressState state;
42 private String version;
49 public static String suformat = "00"; // Format of the suffix number of document did and file name
51 // ==============================================================================================================================
53 // ==============================================================================================================================
55 // Fields initialization class
56 public static class Properties extends Persistent.Properties {
57 // ------------------------------------------------------------
58 private DocumentType type = null;
59 private String did = null; // Only for searching from a given reference
60 private ProjectElement owner = null; // Only for constructing a document
61 private ProjectSettings.Step step = null;
62 private ProgressState state = null;
63 private String name = null;
64 protected String format = null;
65 private String version = null;
66 private User author = null;
67 protected Date date = null;
68 private String summary = null; // Only for versioning a document
69 private String path = null; // Only for searching from a given path
73 public void clear () {
88 public Properties copy () {
89 Properties copy = new Properties();
90 copy.type = this.type;
92 copy.owner = this.owner;
93 copy.step = this.step;
94 copy.state = this.state;
95 copy.name = this.name;
96 copy.format = this.format;
97 copy.version = this.version;
98 copy.author = this.author;
99 copy.date = this.date;
100 copy.summary = this.summary;
101 copy.path = this.path;
104 // - Protected services
106 protected User getAuthor () {
109 protected String getDescription () {
112 protected String getLocalPath () {
115 protected String getReference () {
118 protected ProjectSettings.Step getStep () {
121 protected DocumentType getType () {
124 // - Property setters
126 public Properties setAuthor (User user)
131 public Properties setDate (Date date)
136 public Properties setDescription (String summary) throws InvalidPropertyException
138 if (summary.length() == 0) throw new InvalidPropertyException("description");
139 this.summary = summary;
142 protected Properties setDocument (Document base)
145 step = ProjectSettings.getStep(base.step);
147 format = base.getFormat();
148 state = ProgressState.inWORK; // For incrementing the version number at save time
149 version = base.version;
152 public Properties setExternReference (String ref) throws InvalidPropertyException
154 if (ref.length() == 0) throw new InvalidPropertyException("reference");
155 if (ref.equals(new Revision().toString())) throw new InvalidPropertyException("reference"); // Internal version number
159 public Properties setFormat (String format) throws InvalidPropertyException
161 if (format.length() == 0) throw new InvalidPropertyException("format");
162 this.format = format;
165 // Required only for passing search arguments
166 public Properties setLocalPath (String path) throws InvalidPropertyException
168 if (path.length() == 0) throw new InvalidPropertyException("path");
172 public Properties setName (String name) throws InvalidPropertyException
174 if (name.length() == 0) throw new InvalidPropertyException("name");
178 protected Properties setOwner (ProjectElement owner)
183 // Required only for passing search arguments
184 public Properties setReference (String did) throws InvalidPropertyException
186 if (did.length() == 0) throw new InvalidPropertyException("reference");
190 public Properties setState (ProgressState state) throws InvalidPropertyException
192 if (state == ProgressState.inPROGRESS || state == ProgressState.TEMPLATE) throw new InvalidPropertyException("state"); // Non document states
196 protected Properties setStep (ProjectSettings.Step step)
201 public Properties setType (DocumentType type)
206 // - Global validity check
208 public void checkValidity() throws MissedPropertyException, InvalidPropertyException, MultiplyDefinedException
210 if (type == null) throw new MissedPropertyException("type");
211 if (owner == null) throw new MissedPropertyException("owner");
212 if (step == null) throw new MissedPropertyException("step");
213 if (author == null) throw new MissedPropertyException("author");
214 if (format == null) throw new MissedPropertyException("format");
215 if (owner instanceof Study && !step.appliesTo(Study.class)) throw new InvalidPropertyException("step");
216 if (!type.isContentInto(step)) throw new InvalidPropertyException("step");
217 if (state != null && state != ProgressState.EXTERN) {
218 // inDRAFT, inCHECK or APPROVED + version = imposed version (future use)
219 // inWORK + version = base version incremented at save time (used for versioning)
220 if (version == null) throw new InvalidPropertyException("state");
222 if (version != null) {
223 if (state == null) state = ProgressState.EXTERN;
227 // Database fetch constructor
228 protected Document () {
230 // Internal constructor
231 protected Document (Properties dprop) throws MissedPropertyException, InvalidPropertyException, MultiplyDefinedException {
232 // -------------------------------------
233 super(dprop); // Throws one of the above exception if not valid
234 myfile = new File(null, dprop.format, dprop.date); // The path is initialized below
236 step = dprop.step.getNumber();
238 version = dprop.version;
239 author = dprop.author;
242 lasdate = myfile.getDate(); // Today if not defined in the properties
246 state = ProgressState.inWORK; // Promoted when saving this document
247 version = new Revision().toString();
250 if (dprop.owner instanceof Study) owner = (Study)dprop.owner;
251 else owner = ((Scenario)dprop.owner).getOwnerStudy();
253 ProjectSettings.Step step = ProjectSettings.getStep(this.step);
254 SimpleDateFormat tostring = new SimpleDateFormat("yyyy");
255 String year = tostring.format(owner.getDate());
256 if (name == null) { // Newed document
257 this.name = "%n"; // Named later at publication
258 this.history = -1; // Marks the document as undefined for future assignment
260 String filename = generateEncodedName(owner);
263 path = owner.getReference();
264 did = new StringBuffer(path).append(".%").append(suformat).toString(); // Document reference
265 path = new StringBuffer(year).append("/").append(path).append("/").append(step.getPath()) // File path relative to the repository vault
266 .append(filename).append(".").append(myfile.getFormat()) // File name and extension
268 myfile.changePath(path);
271 // ==============================================================================================================================
272 // Public member functions
273 // ==============================================================================================================================
275 public File getAttachedFile (String format) {
276 // -------------------------------------------
277 List<Relation> exports = getRelations(ConvertsRelation.class);
279 for (Iterator<Relation> i=exports.iterator(); i.hasNext(); ) {
280 File export = (File)i.next().getTo();
281 if (export.getFormat().equals(format)) return export;
286 public User getAuthor () {
287 // ------------------------
291 public Date getCreationDate () {
292 // ------------------------------
293 return myfile.getDate();
296 public Date getLastModificationDate () {
297 // --------------------------------------
301 public String getFormat () {
302 // --------------------------
303 return myfile.getFormat();
306 public Document getPreviousVersion () {
307 // -------------------------------------
308 Relation previous = getFirstRelation(VersionsRelation.class);
309 if (previous != null) return (Document)previous.getTo();
313 public ProgressState getProgressState () {
314 // ----------------------------------------
319 * Returns the path where all physical files attached to this document are saved.
320 * This path is relative to the vault of the repository and include the file name, without extension, common
321 * to all physical files attached to this document.
323 * @return the path of the document
325 public String getRelativePath () {
326 // --------------------------------
327 String[] table = myfile.getRelativePath().split("\\x2E");
328 StringBuffer path = new StringBuffer(table[0]);
329 for (int i=1; i<table.length-1; i++) path.append('.').append(table[i]);
330 return path.toString();
334 * Returns the global unique reference of this document lineage.
335 * The document reference is common to all versions of the document (versioning a document does not change its reference).
336 * It is made of the owner study reference suffixed by a document identifier unique in the scope of the study.
338 * @return the document reference
340 public String getReference () {
341 // -----------------------------
345 public java.io.File getSaveDirectory () {
346 // ---------------------------------------
347 String mypath = Database.getRepositoryVaultPath() + myfile.getRelativePath();
348 String[] table = mypath.split("/");
350 // Cutting the filename
351 StringBuffer path = new StringBuffer(table[0]);
352 for (int i=1; i<table.length-1; i++) path = path.append("/").append(table[i]);
353 return new java.io.File(path.append("/").toString());
356 public File getSourceFile () {
357 // ----------------------------
362 * Returns the stamps such as review and approval attached to this document, if exist.
363 * If several stamps exist, they are returned in ascending order of dates.
365 * @return the stamps of the document in ascending order of dates, or an empty array if no stamp exist.
367 public Timestamp[] getStamps () {
368 // -------------------------------
369 Vector<Timestamp> stamps = new Vector<Timestamp>();
371 for (Iterator<Relation> i=this.getAllRelations().iterator(); i.hasNext(); ) {
372 Relation link = i.next();
373 if (link instanceof StampRelation) stamps.add( ((StampRelation)link).getTo() );
375 Timestamp[] result = stamps.toArray( new Timestamp[stamps.size()] );
376 ComparatorByDate bydate = new Timestamp.ComparatorByDate();
378 Arrays.sort(result, bydate);
383 * Returns the title of this document.
385 * @return the document title, or an empty string is this document is undefined.
386 * @see #isUndefined()
388 public String getTitle () {
389 // -------------------------
390 if (this.isUndefined()) return "";
394 public DocumentType getType () {
395 // ------------------------------
400 * Returns the version number of this document.
401 * The version number, when exists, is either of the internal form (m.n.s) usable for building a Revision object, or any string
402 * in case of external document (document with EXTERN state).<br/>
404 * Note: document slots have a version number equal to "0.0.0".
406 * @return the version number of this document, or null if this is EXTERN.
407 * @see #isUndefined()
409 public String getVersion () {
410 // ---------------------------
415 * Returns true if this document is undefined.
416 * An undefined document is a meta-document created for reserving the persistent reference of a new document before saving
417 * (or importing) this later into the repository.
418 * The working copy of a such document may include this reference.
422 * @see #initialize(Properties)
424 public boolean isUndefined () {
425 // -----------------------------
426 return (history == -1);
429 public boolean isInto (Step container) {
430 // --------------------------------------
431 return (step == container.getNumber());
434 public boolean isPublished () {
435 // -----------------------------
436 return (countag > 0);
439 public boolean isShared () {
440 // --------------------------
441 return (countag + history > 1);
444 public boolean isVersioned () {
445 // -----------------------------
446 return (history > 0);
449 // ==============================================================================================================================
451 // ==============================================================================================================================
453 public static DocumentType createType (DocumentType.Properties tprop) throws MissedPropertyException, InvalidPropertyException, MultiplyDefinedException, RuntimeException {
454 // ---------------------------------------------------------------------
455 //TODO: Check for duplicate definition
456 DocumentType type = new DocumentType(tprop);
457 Session session = Database.getSession();
463 public static Properties extractProperties (java.io.File file) {
464 // --------------------------------------------------------------
465 Properties fprop = new Properties();
466 Reader tool = Toolbox.getReader(file);
468 if (tool != null) try {
469 value = tool.extractProperty("title");
470 if (value != null) fprop.setName(value);
472 value = tool.extractProperty("reference");
473 if (value != null) fprop.setReference(value);
475 catch (Exception e) {
480 @SuppressWarnings("unchecked")
481 public static List<DocumentType> selectAllTypes () {
482 // --------------------------------------------------
483 String query = "from DocumentType";
485 List<DocumentType> types = Database.getSession().createQuery(query).list();
486 for (Iterator<DocumentType> i=types.iterator(); i.hasNext();) {
487 Hibernate.initialize(i.next()); // Supposed fetching document types
492 @SuppressWarnings("unchecked")
493 public static List<DocumentType> selectResultTypes () {
494 // -----------------------------------------------------
495 String query = "from DocumentType where result is not null order by result asc";
497 return Database.getSession().createQuery(query).list();
500 public static DocumentType selectType (String name) {
501 // ---------------------------------------------------
502 String query = new StringBuffer("from DocumentType where name='").append(name).append("'").toString();
504 return (DocumentType)Database.getSession().createQuery(query).uniqueResult();
507 public static DocumentType selectType (int index) {
508 // -------------------------------------------------
509 String query = new StringBuffer("from DocumentType where rid='").append(index).append("'").toString();
511 return (DocumentType)Database.getSession().createQuery(query).uniqueResult();
514 @SuppressWarnings("unchecked")
515 public static List<DocumentType> selectTypesOf (ProjectSettings.Step step) {
516 // --------------------------------------------------------------------------
517 Integer number = step.getNumber();
518 String query = new StringBuffer("from DocumentType").append(" where step like '%-").append(number).append("-%'").toString();
520 List<DocumentType> types = Database.getSession().createQuery(query).list();
521 for (Iterator<DocumentType> i=types.iterator(); i.hasNext();) {
522 Hibernate.initialize(i.next()); // For fetching document types
527 // ==============================================================================================================================
528 // Protected services
529 // ==============================================================================================================================
531 protected ConvertsRelation attach (String format) {
532 // -------------------------------------------------
533 return attach(format, null);
536 protected ConvertsRelation attach (String format, String description) {
537 // ---------------------------------------------------------------------
538 String path = this.getRelativePath();
539 File export = new File(path + "." + format);
540 ConvertsRelation attach = new ConvertsRelation(this, export, description);
541 Session session = Database.getSession();
543 session.save(export);
544 session.save(attach);
546 this.addRelation(attach); // Updates this
551 protected boolean buildReferenceFrom (ProjectElement scope, Document lineage) {
552 // -----------------------------------------------------------------------------
553 if (state != ProgressState.inWORK) return false;
555 Scenario context = null;
556 if (scope instanceof Study) owner = (Study)scope;
558 context = ((Scenario)scope);
559 owner = context.getOwnerStudy();
562 if (context != null && (lineage.isVersioned() || owner.shares(lineage))) {
563 version = new Revision(version).setBranch(context.getReference()).toString();
568 protected boolean buildReferenceFrom (Study scope) {
569 // --------------------------------------------------
570 if (state != ProgressState.inWORK && state != ProgressState.EXTERN) return false;
571 DecimalFormat tostring = new DecimalFormat(suformat);
573 did = did.replace ("%" + suformat, tostring.format(scope.getLastLocalIndex()));
577 protected boolean demote () {
578 // ---------------------------
579 ValidationStep torem;
581 if (state == ProgressState.inCHECK) {
582 state = ProgressState.inDRAFT;
583 torem = ValidationStep.REVIEW;
584 // This operation must not change the version number of documents.
585 // Consequently, inDRAFT documents may have a minor version number equal to zero.
587 if (state == ProgressState.inDRAFT) {
588 state = ProgressState.inWORK;
589 torem = ValidationStep.PROMOTION;
593 for (Iterator<Relation> i=this.getAllRelations().iterator(); i.hasNext(); ) {
594 Relation link = i.next();
595 if (!(link instanceof StampRelation)) continue;
596 if (((StampRelation)link).getStampType() != torem) continue;
600 Database.getSession().update(this);
605 * Increments the reference count of this document following its publication into a Study step.
609 protected void hold () {
610 // ----------------------
612 if (this.isSaved()) Database.getSession().update(this);
616 * Defines this document.
618 * @param dprop the properties of the document
620 * @see Step#createDocument(Properties)
621 * @see #isUndefined()
623 protected void initialize (Properties dprop) throws MissedPropertyException, InvalidPropertyException, NotApplicableException {
624 // --------------------------------------------
625 if (!this.isUndefined()) throw new NotApplicableException("Cannot initialize an existing Document");
626 if (dprop.name == null) throw new MissedPropertyException("name");
627 if (dprop.name.length() == 0) throw new InvalidPropertyException("name");
628 if (dprop.owner == null) throw new MissedPropertyException("owner");
629 // if (dprop.owner instanceof Study && !ProjectSettings.getStep(step).appliesTo(Study.class)) {
630 // throw new InvalidPropertyException("step");
633 myfile.changePath( myfile.getRelativePath().replace("%n", getEncodedRootName((Study)dprop.owner)) );
634 if (history == -1) history = 0;
635 if (dprop.date == null) {
636 Calendar current = Calendar.getInstance();
637 lasdate = current.getTime(); // Today
639 lasdate = dprop.date;
641 Database.getSession().update(this);
644 protected boolean promote (Timestamp stamp) {
645 // -------------------------------------------
646 ProgressState newstate = null;
648 if (state == ProgressState.inWORK) {
649 newstate = ProgressState.inDRAFT; // Promotion to being reviewed
651 if (state == ProgressState.inDRAFT) {
652 newstate = ProgressState.inCHECK; // Promotion to approval
653 Revision myvers = new Revision(version);
654 if (myvers.isMinor()) {
655 version = myvers.incrementAs(newstate).toString();
656 //TODO: If my physical file is programatically editable, update its (property) version number
657 //ISSUE: What about attached files such as PDF if exist, should we remove them ?
660 if (state == ProgressState.inCHECK) {
661 newstate = ProgressState.APPROVED;
663 this.state = newstate;
664 if (stamp != null) this.addRelation( stamp.getContext() );
665 Database.getSession().update(this);
670 * Decrements the reference count of this document following the removal of a Publication from a Study step.
674 protected void release () {
675 // -------------------------
677 if (this.isSaved()) Database.getSession().update(this);
680 protected void rename (String title) throws InvalidPropertyException {
681 // ------------------------------------
682 if (title.length() == 0) throw new InvalidPropertyException("name");
684 Calendar current = Calendar.getInstance();
686 this.lasdate = current.getTime(); // Today
687 Database.getSession().update(this);
690 protected void updateAs (Revision newvers) {
691 // ------------------------------------------
692 version = newvers.setBranch(version).toString(); // Branch names are propagated by the versionning
693 ProgressState newstate = ProgressState.inCHECK;
694 if (newvers.isMinor()) newstate = ProgressState.inWORK;
695 state = null; // Just to tell updateAs(sate) to not increment the version number
699 protected void updateAs (ProgressState state) {
700 // ---------------------------------------------
701 Document previous = null;
703 // Set of version number
704 if (state == ProgressState.EXTERN) {
705 if (this.state != ProgressState.EXTERN) this.version = null; // Strange use-case...
707 Revision myvers = new Revision(version);
708 if (!myvers.isNull()) { // Versionning context
709 for (Iterator<Relation> i=getAllRelations().iterator(); i.hasNext();) {
710 Relation link = i.next();
711 if (!link.getClass().equals(VersionsRelation.class)) continue;
712 previous = (Document)link.getTo(); // Versioned document
716 if (this.state != null) myvers.incrementAs(state); // Incrementation if the reversion number is not imposed
717 this.version = myvers.toString();
719 // Update this document and the previous version, if exit
720 Session session = Database.getSession();
721 if (previous != null) {
722 previous.history += 1;
723 session.update(previous);
726 session.update(this);
729 // protected void upgrade () {
730 // -------------------------
731 // if (this.state != ProgressState.inWORK) return;
733 // Calendar current = Calendar.getInstance();
734 // for (Iterator<Relation> i=getAllRelations().iterator(); i.hasNext();) {
735 // Relation link = i.next();
736 // if (!link.getClass().equals(UsesRelation.class)) continue;
738 // Document used = (Document)link.getTo();
739 // if (!used.isVersioned()) continue;
740 //TODO: Update the uses relation
743 // this.lasdate = current.getTime(); // Today
744 // Database.getSession().update(this);
746 //TODO: Promote documents using this one
749 // ==============================================================================================================================
751 // ==============================================================================================================================
753 private String generateEncodedName (Study scope) {
754 // ------------------------------------------------
755 StringBuffer encoding = new StringBuffer();
756 FileNaming scheme = ProjectSettings.getFileNamingScheme();
757 DecimalFormat tostring = new DecimalFormat(suformat);
759 int number = scope.generateLocalIndex();
761 if (scheme == FileNaming.encoded) {
762 encoding.append(scope.getReference()).append(".").append(tostring.format(number));
763 } else { // title and (temporarily) asis
764 encoding.append(name).append(".").append(tostring.format(number));
766 return encoding.toString();
769 private String getEncodedRootName (Study scope) {
770 // -----------------------------------------------
771 FileNaming scheme = ProjectSettings.getFileNamingScheme();
773 if (scheme == FileNaming.encoded) return scope.getReference();