Salome HOME
Study validation cycle operations are implemented according to the specification.
[tools/siman.git] / Workspace / Siman-Common / src / org / splat / service / DocumentServiceImpl.java
1 /*****************************************************************************
2  * Company         OPEN CASCADE
3  * Application     SIMAN
4  * File            $Id$ 
5  * Creation date   06.10.2012
6  * @author         $Author$
7  * @version        $Revision$
8  *****************************************************************************/
9
10 package org.splat.service;
11
12 import java.text.DecimalFormat;
13 import java.text.SimpleDateFormat;
14 import java.util.Calendar;
15 import java.util.Iterator;
16 import java.util.List;
17
18 import org.hibernate.criterion.Criterion;
19 import org.hibernate.criterion.Restrictions;
20 import org.splat.dal.bo.kernel.Relation;
21 import org.splat.dal.bo.som.ConvertsRelation;
22 import org.splat.dal.bo.som.Document;
23 import org.splat.dal.bo.som.DocumentType;
24 import org.splat.dal.bo.som.File;
25 import org.splat.dal.bo.som.ProgressState;
26 import org.splat.dal.bo.som.ProjectElement;
27 import org.splat.dal.bo.som.Scenario;
28 import org.splat.dal.bo.som.StampRelation;
29 import org.splat.dal.bo.som.Study;
30 import org.splat.dal.bo.som.Timestamp;
31 import org.splat.dal.bo.som.ValidationStep;
32 import org.splat.dal.bo.som.Document.Properties;
33 import org.splat.dal.dao.kernel.RelationDAO;
34 import org.splat.dal.dao.som.DocumentDAO;
35 import org.splat.dal.dao.som.DocumentTypeDAO;
36 import org.splat.dal.dao.som.FileDAO;
37 import org.splat.dal.dao.som.StudyDAO;
38 import org.splat.kernel.InvalidPropertyException;
39 import org.splat.kernel.MissedPropertyException;
40 import org.splat.kernel.NotApplicableException;
41 import org.splat.log.AppLogger;
42 import org.splat.manox.Reader;
43 import org.splat.manox.Toolbox;
44 import org.splat.service.technical.ProjectSettingsService;
45 import org.splat.service.technical.RepositoryService;
46 import org.splat.service.technical.ProjectSettingsServiceImpl.FileNaming;
47 import org.splat.som.Revision;
48 import org.springframework.transaction.annotation.Transactional;
49
50 /**
51  * Document service implementation.
52  * 
53  * @author <a href="mailto:roman.kozlov@opencascade.com">Roman Kozlov (RKV)</a>
54  * 
55  */
56 public class DocumentServiceImpl implements DocumentService {
57
58         /**
59          * The logger for the service.
60          */
61         public final static AppLogger LOG = AppLogger
62                         .getLogger(DocumentServiceImpl.class);
63
64         /**
65          * Injected study service.
66          */
67         private StudyService _studyService;
68         /**
69          * Injected project settings service.
70          */
71         private ProjectSettingsService _projectSettings;
72         /**
73          * Injected repository service.
74          */
75         private RepositoryService _repositoryService;
76         /**
77          * Injected document DAO.
78          */
79         private DocumentDAO _documentDAO;
80         /**
81          * Injected relation DAO.
82          */
83         private RelationDAO _relationDAO;
84         /**
85          * Injected document type DAO.
86          */
87         private DocumentTypeDAO _documentTypeDAO;
88         /**
89          * Injected file DAO.
90          */
91         private FileDAO _fileDAO;
92         /**
93          * Injected study DAO.
94          */
95         private StudyDAO _studyDAO;
96
97         /**
98          * {@inheritDoc}
99          * 
100          * @see org.splat.service.DocumentService#selectDocument(long)
101          */
102         @Transactional(readOnly = true)
103         public Document selectDocument(final long index) {
104                 return getDocumentDAO().get(index);
105         }
106
107         /**
108          * {@inheritDoc}
109          * 
110          * @see org.splat.service.DocumentService#selectDocument(java.lang.String, java.lang.String)
111          */
112         @Transactional(readOnly = true)
113         public Document selectDocument(final String refid, final String version) {
114                 Criterion aCondition = Restrictions.and(Restrictions.eq("did", refid),
115                                 Restrictions.eq("version", version));
116                 return getDocumentDAO().findByCriteria(aCondition);
117         }
118
119         /**
120          * {@inheritDoc}
121          * 
122          * @see org.splat.service.DocumentService#generateDocumentId(org.splat.dal.bo.som.Document, org.splat.dal.bo.som.Document.Properties)
123          */
124         @Transactional
125         public void generateDocumentId(final Document aDoc, final Properties dprop) {
126                 Study owner = dprop.getOwner().getOwnerStudy();
127
128                 // Synchronize the object with the current Hibernate session.
129                 owner.setLastLocalIndex(getStudyDAO().get(owner.getIndex())
130                                 .getLastLocalIndex());
131
132                 SimpleDateFormat tostring = new SimpleDateFormat("yyyy"); // RKV: NOPMD: TODO: Use locale here?
133                 String year = tostring.format(owner.getDate());
134                 String filename = generateEncodedName(aDoc, owner);
135                 String path = owner.getReference();
136                 ProjectSettingsService.Step step = getProjectSettings().getStep(
137                                 aDoc.getStep());
138                 aDoc.setDid(new StringBuffer(path).append(".%").append(
139                                 Document.suformat).toString()); // Document reference
140                 StringBuffer pathBuf = new StringBuffer(year).append("/").append(path)
141                                 .append("/").append(step.getPath())
142                                 // File path relative to the repository vault
143                                 .append(filename);
144                 if (!FileNaming.asis.equals(getProjectSettings().getFileNamingScheme())) {
145                         // Use format as extension if file naming strategy is not "asis".
146                         pathBuf.append(".").append(aDoc.getFile().getFormat()); // File name and extension
147                 }
148                 aDoc.getFile().changePath(pathBuf.toString());
149                 owner = getStudyDAO().merge(owner);
150         }
151
152         /**
153          * Generate encoded document file name according to the naming scheme from project settings.
154          * 
155          * @param aDoc
156          *            the document
157          * @param scope
158          *            the study
159          * @return document reference name
160          */
161         private String generateEncodedName(final Document aDoc, final Study scope) {
162                 StringBuffer encoding = new StringBuffer();
163                 FileNaming scheme = getProjectSettings().getFileNamingScheme();
164                 DecimalFormat tostring = new DecimalFormat(Document.suformat);
165
166                 int number = getStudyService().generateLocalIndex(scope);
167
168                 if (scheme == FileNaming.encoded) {
169                         encoding.append(scope.getReference()).append("_").append(
170                                         tostring.format(number));
171                 } else { // title and (temporarily) asis
172                         encoding.append(tostring.format(number)).append("_").append(
173                                         aDoc.getFile().getName());
174                 }
175                 return encoding.toString();
176         }
177
178         /**
179          * Get encoded root name for a document file.
180          * 
181          * @param aDoc
182          *            the document
183          * @param scope
184          *            the study
185          * @return file name
186          */
187         private String getEncodedRootName(final Document aDoc, final Study scope) {
188                 FileNaming scheme = getProjectSettings().getFileNamingScheme();
189                 String res;
190                 if (scheme == FileNaming.encoded) {
191                         res = scope.getReference();
192                 } else if (scheme == FileNaming.asis) {
193                         res = aDoc.getFile().getName();
194                 } else {
195                         res = aDoc.getTitle();
196                 }
197                 return res;
198         }
199
200         /**
201          * {@inheritDoc}
202          * 
203          * @see org.splat.service.DocumentService#initialize(org.splat.dal.bo.som.Document, org.splat.dal.bo.som.Document.Properties)
204          */
205         @Transactional
206         public void initialize(final Document aDoc, final Properties dprop)
207                         throws MissedPropertyException, InvalidPropertyException,
208                         NotApplicableException {
209                 if (!aDoc.isUndefined()) {
210                         throw new NotApplicableException(
211                                         "Cannot initialize an existing Document");
212                 }
213                 if (dprop.getName() == null) {
214                         throw new MissedPropertyException("name");
215                 }
216                 if (dprop.getName().length() == 0) {
217                         throw new InvalidPropertyException("name");
218                 }
219                 if (dprop.getOwner() == null) {
220                         throw new MissedPropertyException("owner");
221                 }
222                 // if (dprop.owner instanceof Study && !ProjectSettings.getStep(step).appliesTo(Study.class)) {
223                 // throw new InvalidPropertyException("step");
224                 // }
225                 aDoc.setTitle(dprop.getName());
226                 aDoc.getFile().changePath(
227                                 aDoc.getFile().getRelativePath().replace("%n",
228                                                 getEncodedRootName(aDoc, (Study) dprop.getOwner())));
229                 if (aDoc.getHistory() == -1) {
230                         aDoc.setHistory(0);
231                 }
232                 if (dprop.getDate() == null) {
233                         Calendar current = Calendar.getInstance();
234                         aDoc.setLastModificationDate(current.getTime()); // Today
235                 } else {
236                         aDoc.setLastModificationDate(dprop.getDate());
237                 }
238                 getDocumentDAO().update(aDoc);
239         }
240
241         /**
242          * {@inheritDoc}
243          * 
244          * @see org.splat.service.DocumentService#getSaveDirectory(org.splat.dal.bo.som.Document)
245          */
246         public java.io.File getSaveDirectory(final Document aDoc) {
247                 String mypath = getRepositoryService().getRepositoryVaultPath()
248                                 + aDoc.getSourceFile().getRelativePath();
249                 String[] table = mypath.split("/");
250
251                 // Cutting the filename
252                 StringBuffer path = new StringBuffer(table[0]);
253                 for (int i = 1; i < table.length - 1; i++) {
254                         path = path.append("/").append(table[i]);
255                 }
256                 return new java.io.File(path.append("/").toString());
257         }
258
259         /**
260          * Extract title and reference properties from the given file.
261          * 
262          * @param file
263          *            the file to parse
264          * @return the extracted properties
265          */
266         public Properties extractProperties(final java.io.File file) {
267                 Properties fprop = new Properties();
268                 Reader tool = Toolbox.getReader(file);
269                 String value;
270                 if (tool != null) {
271                         try {
272                                 value = tool.extractProperty("title");
273                                 if (value != null) {
274                                         fprop.setName(value);
275                                 }
276
277                                 value = tool.extractProperty("reference");
278                                 if (value != null) {
279                                         fprop.setReference(value);
280                                 }
281                         } catch (Exception e) {
282                                 LOG.debug(e.getMessage(), e);
283                         }
284                 }
285                 return fprop;
286         }
287
288         /**
289          * Create "Converts" relation for the given document and the given format.
290          * 
291          * @param aDoc
292          *            the document
293          * @param format
294          *            the format
295          * @return the created "Converts" relation
296          */
297         @Transactional
298         public ConvertsRelation attach(final Document aDoc, final String format) {
299                 return attach(aDoc, format, null);
300         }
301
302         /**
303          * Create "Converts" relation for the given document, format and description.
304          * 
305          * @param aDoc
306          *            the document
307          * @param format
308          *            the format
309          * @param description
310          *            the description of the relation
311          * @return the created "Converts" relation
312          */
313         @Transactional
314         public ConvertsRelation attach(final Document aDoc, final String format,
315                         final String description) {
316                 String path = aDoc.getRelativePath();
317                 File export = new File(path + "." + format);
318                 ConvertsRelation attach = new ConvertsRelation(aDoc, export,
319                                 description);
320
321                 getFileDAO().create(export);
322                 getRelationDAO().create(attach);
323
324                 aDoc.getAllRelations().add(attach);
325                 getDocumentDAO().merge(aDoc);
326
327                 return attach;
328         }
329
330         /**
331          * Build a reference (document id) for the given document in the given study basing on the given original document.
332          * 
333          * @param aDoc
334          *            the document to set set the new reference to
335          * @param scope
336          *            the study
337          * @param lineage
338          *            the original document
339          * @return true if the new reference is set
340          */
341         @Transactional
342         public boolean buildReferenceFrom(final Document aDoc,
343                         final ProjectElement scope, final Document lineage) {
344                 boolean res = (aDoc.getProgressState() == ProgressState.inWORK);
345                 if (res) {
346                         Study owner = scope.getOwnerStudy();
347                         Scenario context = null;
348                         if (scope instanceof Scenario) {
349                                 context = ((Scenario) scope);
350                         }
351                         aDoc.setDid(lineage.getDid());
352                         if (context != null
353                                         && (lineage.isVersioned() || owner.shares(lineage))) {
354                                 aDoc.setVersion(new Revision(aDoc.getVersion()).setBranch(
355                                                 context.getReference()).toString());
356                         }
357                 }
358                 return res;
359         }
360
361         /**
362          * Build a reference (document id) for the given document in the given study.
363          * 
364          * @param aDoc
365          *            the document to set set the new reference to
366          * @param scope
367          *            the study
368          * @return true if the new reference is set
369          */
370         @Transactional
371         public boolean buildReferenceFrom(final Document aDoc, final Study scope) {
372                 if (aDoc.getProgressState() != ProgressState.inWORK
373                                 && aDoc.getProgressState() != ProgressState.EXTERN) {
374                         return false;
375                 }
376                 DecimalFormat tostring = new DecimalFormat(Document.suformat);
377
378                 aDoc.setDid(aDoc.getDid().replace("%" + Document.suformat,
379                                 tostring.format(scope.getLastLocalIndex())));
380                 return true;
381         }
382
383         /**
384          * Demote a document.
385          * 
386          * @param aDoc
387          *            the document to demote
388          * @return true if demoting succeeded
389          */
390         @Transactional
391         public boolean demote(final Document aDoc) {
392                 ValidationStep torem;
393
394                 if (aDoc.getProgressState() == ProgressState.inCHECK) {
395                         aDoc.setProgressState(ProgressState.inDRAFT);
396                         torem = ValidationStep.REVIEW;
397                         // This operation must not change the version number of documents.
398                         // Consequently, inDRAFT documents may have a minor version number equal to zero.
399                 } else if (aDoc.getProgressState() == ProgressState.inDRAFT) {
400                         aDoc.setProgressState(ProgressState.inWORK);
401                         torem = ValidationStep.PROMOTION;
402                 } else {
403                         return false;
404                 }
405                 for (Iterator<Relation> i = aDoc.getAllRelations().iterator(); i
406                                 .hasNext();) {
407                         Relation link = i.next();
408                         if (!(link instanceof StampRelation)) {
409                                 continue;
410                         }
411                         if (((StampRelation) link).getStampType() != torem) {
412                                 continue;
413                         }
414                         i.remove();
415                         break;
416                 }
417                 getDocumentDAO().update(aDoc);
418                 return true;
419         }
420
421         /**
422          * Promote a document.
423          * 
424          * @param aDoc
425          *            the document to promote
426          * @param stamp
427          *            the timestamp of the promotion
428          * @return true if promotion succeeded
429          */
430         @Transactional
431         public boolean promote(final Document aDoc, final Timestamp stamp) {
432                 ProgressState newstate = null;
433
434                 if (aDoc.getProgressState() == ProgressState.inWORK) {
435                         newstate = ProgressState.inDRAFT; // Promotion to being reviewed
436                 } else if (aDoc.getProgressState() == ProgressState.inDRAFT) {
437                         newstate = ProgressState.inCHECK; // Promotion to approval
438                         Revision myvers = new Revision(aDoc.getVersion());
439                         if (myvers.isMinor()) {
440                                 aDoc.setVersion(myvers.incrementAs(newstate).toString());
441                                 // TODO: If my physical file is programatically editable, update its (property) version number
442                                 // ISSUE: What about attached files such as PDF if exist, should we remove them ?
443                         }
444                 } else if (aDoc.getProgressState() == ProgressState.inCHECK) {
445                         newstate = ProgressState.APPROVED;
446                 }
447                 aDoc.setProgressState(newstate);
448                 if (stamp != null) {
449                         // RKV: aDoc.addRelation(stamp.getContext());
450                         aDoc.getAllRelations().add(stamp.getContext());
451                 }
452                 getDocumentDAO().update(aDoc);
453                 return true;
454         }
455
456         /**
457          * Increments the reference count of this document following its publication in a Study step.
458          * 
459          * @param aDoc
460          *            the document to hold
461          * @see #release()
462          */
463         @Transactional
464         public void hold(final Document aDoc) {
465                 aDoc.setCountag(aDoc.getCountag() + 1);
466                 if (aDoc.isSaved()) {
467                         getDocumentDAO().merge(aDoc);
468                 }
469         }
470
471         /**
472          * Decrements the reference count of this document following the removal of a Publication from a Study step.
473          * 
474          * @param aDoc
475          *            the document to release
476          * @see #hold()
477          */
478         @Transactional
479         public void release(final Document aDoc) {
480                 aDoc.setCountag(aDoc.getCountag() - 1);
481                 if (aDoc.isSaved()) {
482                         getDocumentDAO().merge(aDoc);
483                 }
484         }
485
486         /**
487          * Rename a document.
488          * 
489          * @param aDoc
490          *            the document to rename
491          * @param title
492          *            the new document title
493          * @throws InvalidPropertyException
494          *             if the new title is empty
495          */
496         @Transactional
497         public void rename(final Document aDoc, final String title)
498                         throws InvalidPropertyException {
499                 if (title.length() == 0) {
500                         throw new InvalidPropertyException("name");
501                 }
502
503                 Calendar current = Calendar.getInstance();
504                 aDoc.setTitle(title);
505                 aDoc.setLastModificationDate(current.getTime()); // Today
506                 getDocumentDAO().update(aDoc);
507         }
508
509         /**
510          * Update a version of the given document.
511          * 
512          * @param aDoc
513          *            the document
514          * @param newvers
515          *            the new version
516          */
517         public void updateAs(final Document aDoc, final Revision newvers) {
518                 aDoc.setVersion(newvers.setBranch(aDoc.getVersion()).toString()); // Branch names are propagated by the versionning
519                 ProgressState newstate = ProgressState.inCHECK;
520                 if (newvers.isMinor()) {
521                         newstate = ProgressState.inWORK;
522                 }
523                 aDoc.setProgressState(null); // Just to tell updateAs(state) to not increment the version number
524                 updateAs(aDoc, newstate);
525         }
526
527         /**
528          * Update a state of the given document.
529          * 
530          * @param aDoc
531          *            the document
532          * @param state
533          *            the new state
534          */
535         @Transactional
536         public void updateAs(final Document aDoc, final ProgressState state) {
537                 Document previous = null;
538
539                 // Set of version number
540                 if (state == ProgressState.EXTERN) {
541                         if (aDoc.getProgressState() != ProgressState.EXTERN) {
542                                 aDoc.setVersion(null); // Strange use-case...
543                         }
544                 } else {
545                         Revision myvers = new Revision(aDoc.getVersion());
546                         if (!myvers.isNull()) { // Versionning context
547                                 previous = aDoc.getPreviousVersion();
548                         }
549                         if (aDoc.getProgressState() != null) {
550                                 myvers.incrementAs(state); // Incrementation if the reversion number is not imposed
551                         }
552                         aDoc.setVersion(myvers.toString());
553                 }
554                 // Update this document and the previous version, if exist
555                 if (previous != null) {
556                         previous.setHistory(previous.getHistory() + 1);
557                         getDocumentDAO().merge(previous);
558                 }
559                 aDoc.setProgressState(state);
560                 // RKV: getDocumentDAO().update(aDoc);
561         }
562
563         // protected void upgrade () {
564         // -------------------------
565         // if (this.state != ProgressState.inWORK) return;
566         //
567         // Calendar current = Calendar.getInstance();
568         // for (Iterator<Relation> i=getAllRelations().iterator(); i.hasNext();) {
569         // Relation link = i.next();
570         // if (!link.getClass().equals(UsesRelation.class)) continue;
571         //
572         // Document used = (Document)link.getTo();
573         // if (!used.isVersioned()) continue;
574         // TODO: Update the uses relation
575         // }
576         // this.promote();
577         // this.lasdate = current.getTime(); // Today
578         // Database.getSession().update(this);
579         //
580         // TODO: Promote documents using this one
581         // }
582
583         /**
584          * Checks if documents of this type are result of a study. A document is the result of a study when it is the result of the last step of
585          * the study.
586          * 
587          * @param aType
588          *            the document type
589          * @return true if documents of this type are result of a study.
590          * @see #isStepResult()
591          * @see #isResultOf(org.splat.service.technical.ProjectSettingsServiceImpl.Step)
592          */
593         public boolean isStudyResult(final DocumentType aType) {
594                 List<ProjectSettingsService.Step> step = getProjectSettings()
595                                 .getAllSteps();
596                 ProjectSettingsService.Step lastep = step.get(step.size() - 1);
597                 return (aType.isResultOf(lastep));
598         }
599
600         /**
601          * Get document by its path.
602          * 
603          * @param path
604          *            the document path
605          * @return the document if found or null
606          */
607         @Transactional(readOnly = true)
608         public Document getDocumentByPath(String path) {
609                 String[] parse = path.split("'");
610
611                 path = parse[0];
612                 for (int i = 1; i < parse.length; i++) {
613                         path = path + "''" + parse[i];
614                 }
615                 Criterion aCondition = Restrictions.eq("path", path);
616                 return getDocumentDAO().findByCriteria(aCondition);
617         }
618
619         /**
620          * Get the studyService.
621          * 
622          * @return the studyService
623          */
624         public StudyService getStudyService() {
625                 return _studyService;
626         }
627
628         /**
629          * Set the studyService.
630          * 
631          * @param studyService
632          *            the studyService to set
633          */
634         public void setStudyService(final StudyService studyService) {
635                 _studyService = studyService;
636         }
637
638         /**
639          * Get project settings.
640          * 
641          * @return Project settings service
642          */
643         private ProjectSettingsService getProjectSettings() {
644                 return _projectSettings;
645         }
646
647         /**
648          * Set project settings service.
649          * 
650          * @param projectSettingsService
651          *            project settings service
652          */
653         public void setProjectSettings(
654                         final ProjectSettingsService projectSettingsService) {
655                 _projectSettings = projectSettingsService;
656         }
657
658         /**
659          * Get the documentDAO.
660          * 
661          * @return the documentDAO
662          */
663         public DocumentDAO getDocumentDAO() {
664                 return _documentDAO;
665         }
666
667         /**
668          * Set the documentDAO.
669          * 
670          * @param documentDAO
671          *            the documentDAO to set
672          */
673         public void setDocumentDAO(final DocumentDAO documentDAO) {
674                 _documentDAO = documentDAO;
675         }
676
677         /**
678          * Get the repositoryService.
679          * 
680          * @return the repositoryService
681          */
682         public RepositoryService getRepositoryService() {
683                 return _repositoryService;
684         }
685
686         /**
687          * Set the repositoryService.
688          * 
689          * @param repositoryService
690          *            the repositoryService to set
691          */
692         public void setRepositoryService(final RepositoryService repositoryService) {
693                 _repositoryService = repositoryService;
694         }
695
696         /**
697          * Get the documentTypeDAO.
698          * 
699          * @return the documentTypeDAO
700          */
701         public DocumentTypeDAO getDocumentTypeDAO() {
702                 return _documentTypeDAO;
703         }
704
705         /**
706          * Set the documentTypeDAO.
707          * 
708          * @param documentTypeDAO
709          *            the documentTypeDAO to set
710          */
711         public void setDocumentTypeDAO(final DocumentTypeDAO documentTypeDAO) {
712                 _documentTypeDAO = documentTypeDAO;
713         }
714
715         /**
716          * Get the fileDAO.
717          * 
718          * @return the fileDAO
719          */
720         public FileDAO getFileDAO() {
721                 return _fileDAO;
722         }
723
724         /**
725          * Set the fileDAO.
726          * 
727          * @param fileDAO
728          *            the fileDAO to set
729          */
730         public void setFileDAO(final FileDAO fileDAO) {
731                 _fileDAO = fileDAO;
732         }
733
734         /**
735          * Get the studyDAO.
736          * 
737          * @return the studyDAO
738          */
739         public StudyDAO getStudyDAO() {
740                 return _studyDAO;
741         }
742
743         /**
744          * Set the studyDAO.
745          * 
746          * @param studyDAO
747          *            the studyDAO to set
748          */
749         public void setStudyDAO(final StudyDAO studyDAO) {
750                 _studyDAO = studyDAO;
751         }
752
753         /**
754          * Get the relationDAO.
755          * 
756          * @return the relationDAO
757          */
758         public RelationDAO getRelationDAO() {
759                 return _relationDAO;
760         }
761
762         /**
763          * Set the relationDAO.
764          * 
765          * @param relationDAO
766          *            the relationDAO to set
767          */
768         public void setRelationDAO(final RelationDAO relationDAO) {
769                 _relationDAO = relationDAO;
770         }
771
772 }