KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jahia > services > fields > ContentField


1 /*
2  * ____.
3  * __/\ ______| |__/\. _______
4  * __ .____| | \ | +----+ \
5  * _______| /--| | | - \ _ | : - \_________
6  * \\______: :---| : : | : | \________>
7  * |__\---\_____________:______: :____|____:_____\
8  * /_____|
9  *
10  * . . . i n j a h i a w e t r u s t . . .
11  *
12  *
13  *
14  * ----- BEGIN LICENSE BLOCK -----
15  * Version: JCSL 1.0
16  *
17  * The contents of this file are subject to the Jahia Community Source License
18  * 1.0 or later (the "License"); you may not use this file except in
19  * compliance with the License. You may obtain a copy of the License at
20  * http://www.jahia.org/license
21  *
22  * Software distributed under the License is distributed on an "AS IS" basis,
23  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
24  * for the rights, obligations and limitations governing use of the contents
25  * of the file. The Original and Upgraded Code is the Jahia CMS and Portal
26  * Server. The developer of the Original and Upgraded Code is JAHIA Ltd. JAHIA
27  * Ltd. owns the copyrights in the portions it created. All Rights Reserved.
28  *
29  * The Shared Modifications are Jahia View Helper.
30  *
31  * The Developer of the Shared Modifications is Jahia Solution S�rl.
32  * Portions created by the Initial Developer are Copyright (C) 2002 by the
33  * Initial Developer. All Rights Reserved.
34  *
35  * Contributor(s):
36  * 13-AUG-2003, Jahia Solutions Sarl, Fulco Houkes
37  *
38  * ----- END LICENSE BLOCK -----
39  */

40
41 package org.jahia.services.fields;
42
43 import org.jahia.content.*;
44 import org.jahia.bin.Jahia;
45 import org.jahia.data.fields.JahiaField;
46 import org.jahia.data.fields.JahiaFieldDefinition;
47 import org.jahia.engines.EngineMessage;
48 import org.jahia.exceptions.JahiaException;
49 import org.jahia.params.ParamBean;
50 import org.jahia.registries.JahiaListenersRegistry;
51 import org.jahia.registries.JahiaFieldDefinitionsRegistry;
52 import org.jahia.registries.ServicesRegistry;
53 import org.jahia.services.acl.ACLResourceInterface;
54 import org.jahia.services.acl.JahiaBaseACL;
55 import org.jahia.services.containers.ContentContainer;
56 import org.jahia.services.containers.FieldsChangeEventListener;
57 import org.jahia.services.pages.ContentPage;
58 import org.jahia.services.sites.JahiaSite;
59 import org.jahia.services.sites.SiteLanguageSettings;
60 import org.jahia.services.usermanager.JahiaUser;
61 import org.jahia.services.version.*;
62 import org.jahia.utils.xml.XMLSerializationOptions;
63 import org.jahia.utils.xml.XmlWriter;
64
65 import java.io.IOException JavaDoc;
66 import java.io.Serializable JavaDoc;
67 import java.util.*;
68
69 /**
70  * This class represents a Field in Jahia (it should replace JahiaFields in the
71  * future). There can't be two ContentField objects with the same ID in memory.
72  */

73 public abstract class ContentField extends ContentObject
74         implements ACLResourceInterface, PageReferenceableInterface, Serializable JavaDoc {
75
76     private static org.apache.log4j.Logger logger =
77             org.apache.log4j.Logger.getLogger (ContentField.class);
78
79     private static final String JavaDoc NO_VALUE = "<no_value>";
80
81     private int aclID;
82     private JahiaBaseACL acl;
83     private int fieldType;
84     private int jahiaID;
85     private int pageID;
86     private int containerID;
87     private int fieldDefID;
88     private int connectType;
89     // this is a Vector of EntryState listing ALL different Entry States
90
// for a field, that are ACTIVE or STAGED (including all languages)
91
private Vector activeAndStagingEntryStates;
92     // this is a Vector of EntryState listing ALL different Entry States
93
// for VERSIONING (inactive) versions of the field. This Vector can
94
// be NULL if such information is not loaded, else every versioning
95
// entry state must be in.
96
private Vector versioningEntryStates;
97     // key = the EntryState, value = the Value from the database (DBValue class)
98
// actually the key is the EntryState as stored in the DB. if a field
99
// is modified here, the key will remain the same (with old versionID) until
100
// it has been synched with the DB with the CFS.setField method.
101
private Hashtable loadedDBValues;
102     private Hashtable theProperties; // not handled yet
103

104     static {
105         JahiaObject.registerType (ContentFieldKey.FIELD_TYPE,
106                 ContentField.class.getName ());
107     }
108
109     public static ContentObject getChildInstance (String JavaDoc IDInType) {
110         try {
111             return getField (Integer.parseInt (IDInType));
112         } catch (JahiaException je) {
113             logger.debug ("Error retrieving field instance for id : " + IDInType, je);
114         }
115         return null;
116     }
117
118
119     public int getAclID () {
120         return aclID;
121     }
122
123     public int getType () {
124         return fieldType;
125     }
126
127     public int getSiteID () {
128         return jahiaID;
129     }
130
131     public int getPageID () {
132         return pageID;
133     }
134
135     public int getContainerID () {
136         return containerID;
137     }
138
139     public int getFieldDefID () {
140         return fieldDefID;
141     }
142
143     public int getConnectType () {
144         return connectType;
145     }
146
147     //-------------------------------------------------------------------------
148
/**
149      * Returns the ContentPage ancestor.
150      *
151      * @return Return the ContentPage ancestor.
152      */

153     public ContentPage getPage () throws JahiaException {
154         return ContentPage.getPage (this.getPageID ());
155     }
156
157     /**
158      * Returns the identifier of the Content Definition for Content object.
159      *
160      * @param loadRequest
161      *
162      * @return
163      */

164     public int getDefinitionID (EntryLoadRequest loadRequest) {
165         return this.getFieldDefID ();
166     }
167
168     /**
169      * Returns the Definition Key of the Content Definition for this Content object.
170      * This is a ContainerDefinition
171      *
172      * @param loadRequest
173      *
174      * @return
175      */

176     public ObjectKey getDefinitionKey (EntryLoadRequest loadRequest) {
177         int defID = getDefinitionID (loadRequest);
178         FieldDefinitionKey fieldDefKey = (FieldDefinitionKey)
179                 FieldDefinitionKey.getChildInstance (String.valueOf (defID));
180         return fieldDefKey;
181     }
182
183     public final JahiaBaseACL getACL () {
184         if (acl == null) {
185             try {
186                 acl = new JahiaBaseACL (getAclID ());
187             } catch (Throwable JavaDoc t) {
188                 t.printStackTrace ();
189             }
190         }
191         return acl;
192     }
193
194     /**
195      * This constructor can only be called by extensions of this class
196      * such as ContentBigTextField, etc.
197      * Warning : all constructors signatures must be equal since they are called
198      * via class reflection in ContentFieldTools.createFieldInstance !
199      */

200     protected ContentField (int fieldID, int aJahiaID, int aPageID, int ctnid, int aFieldDefID,
201                             int typeField, int aConnectType, int rights, Vector anActiveAndStagingEntryStates,
202                             Hashtable activeAndStagedDBValues) {
203         super (new ContentFieldKey (fieldID));
204         this.jahiaID = aJahiaID;
205         this.pageID = aPageID;
206         this.containerID = ctnid;
207         this.fieldDefID = aFieldDefID;
208         this.fieldType = typeField;
209         this.connectType = aConnectType;
210         this.aclID = rights;
211         this.activeAndStagingEntryStates = anActiveAndStagingEntryStates;
212         this.versioningEntryStates = null;
213         this.theProperties = null; // FIXME
214

215         this.loadedDBValues = new Hashtable (activeAndStagedDBValues);
216
217         this.acl = null;
218     }
219
220     /**
221      * No arg constructor used to comply with Serialization requirements.
222      */

223     protected ContentField() {
224
225     }
226
227     /**
228      * Returns an entry state for the field corresponding to the EntryLoadRequest
229      * parameter. This is used notably to create JahiaField facades from
230      * ContentFields.
231      *
232      * @param loadRequest an Entry load request for which to resolve an entry
233      * state
234      *
235      * @return an ContentFieldEntryState object that contains the resolved
236      * entry for the content field.
237      *
238      * @throws JahiaException
239      */

240     public ContentObjectEntryState getEntryState (EntryLoadRequest loadRequest)
241             throws JahiaException {
242         Vector entryStateables = new Vector (activeAndStagingEntryStates);
243         if (loadRequest.getWorkflowState () < 1) // we want a backuped version
244
{
245             loadVersioningEntryStates ();
246             // so we must add the backuped field entry state to the list aswell
247
entryStateables.addAll (versioningEntryStates);
248         }
249         // faire le resolving STAGING/ACTIVE/MULTILANGUE a partir de loadedDBValues
250
return (ContentObjectEntryState) ServicesRegistry.getInstance ().getJahiaVersionService ().resolveEntry (
251                 entryStateables, loadRequest);
252     }
253
254     /**
255      * Gets the String representation of this field. In case of an Application,
256      * it will be the output of the application, in case of a bigtext it will
257      * be the full content of the bigtext, etc.
258      */

259     public String JavaDoc getValue (ParamBean jParams) throws JahiaException {
260         ContentObjectEntryState entryState = getEntryState (jParams.getEntryLoadRequest ());
261         if (entryState == null) {
262             return null;
263         }
264         return this.getValue (jParams, entryState);
265     }
266
267     public String JavaDoc getValue (ContentObjectEntryState entryState)
268             throws JahiaException {
269         return this.getValue (null, entryState);
270     }
271
272     /**
273      * Mark a language for deletion, or removes all the content definitively
274      * if there is no active version.
275      *
276      * @param user the reference to the user
277      * @param languageCode the language code
278      * @param stateModificationContext
279      *
280      * @throws JahiaException when an internal error occured.
281      */

282     public void markLanguageForDeletion (JahiaUser user,
283                                          String JavaDoc languageCode,
284                                          StateModificationContext stateModificationContext)
285             throws JahiaException {
286
287         /**
288          * NK:
289          * Testing if the field will be completely deleted has no meaning,
290          * because some fields are SHARED, but their content can be MULTILANGUAL (i.e page fields).
291          * Instead, we should state that :
292          * a field will be completely deleted only if its parent ( page or container )
293          * is going to be completely deleted
294          */

295         boolean stateModified = false;
296         if (ContentObject.SHARED_LANGUAGE.equals (languageCode)
297                 || (!this.isShared () && this.willBeCompletelyDeleted (languageCode, null))) {
298             stateModified = true;
299             stateModificationContext.pushAllLanguages (true);
300         }
301
302         Set languageCodes = new HashSet ();
303         if (ContentObject.SHARED_LANGUAGE.equals (languageCode)
304                 && !this.isShared ()) {
305             languageCodes.addAll (getStagingLanguages (true, false));
306             Iterator languageCodeIter = languageCodes.iterator ();
307             while (languageCodeIter.hasNext ()) {
308                 String JavaDoc curLanguageCode = (String JavaDoc) languageCodeIter.next ();
309                 markContentLanguageForDeletion (user, curLanguageCode,
310                         stateModificationContext);
311             }
312         } else {
313             markContentLanguageForDeletion (user, languageCode, stateModificationContext);
314         }
315
316         languageCodes = new HashSet ();
317         languageCodes.add (languageCode);
318         if (stateModified && stateModificationContext.isAllLanguages ()) {
319             languageCodes.addAll (getStagingLanguages (false));
320         }
321         if ((this.isShared () &&
322                 !this.willAllChildsBeCompletelyDeleted (user, languageCode, null))
323                 || (this.isShared () && !ContentObject.SHARED_LANGUAGE.equals (languageCode))) {
324             // we are not going to delete this content field if
325
// it is shared and there are still some undeleted content
326
// or the requested lang is not SHARED
327

328             // But we have to create a staged entry to be able to activate this field's staged content
329
// we have to create a staged entry to be able to active mark for deleted on this container's fields
330
if (this.getStagingLanguages (false, true).isEmpty ()) {
331                 // if no staging or nor marked for delete exists
332
Iterator iterator = this.getActiveAndStagingEntryStates ().iterator ();
333                 if (iterator.hasNext ()) {
334                     ContentObjectEntryState fromEntryState =
335                             (ContentObjectEntryState) iterator.next ();
336                     ContentObjectEntryState toEntryState =
337                             new ContentObjectEntryState (ContentObjectEntryState
338                             .WORKFLOW_STATE_START_STAGING, 0,
339                                     ContentObject.SHARED_LANGUAGE);
340                     this.copyEntry (fromEntryState, toEntryState);
341                 }
342             }
343
344             if (stateModified) {
345                 stateModificationContext.popAllLanguages ();
346             }
347         }
348
349         Iterator languageCodeIter = languageCodes.iterator ();
350         while (languageCodeIter.hasNext ()) {
351             String JavaDoc curLanguageCode = (String JavaDoc) languageCodeIter.next ();
352
353             boolean foundInActive = false;
354             for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
355                 ContentObjectEntryState thisEntryState = (
356                         ContentObjectEntryState) activeAndStagingEntryStates.get (i);
357                 if (thisEntryState.isActive ()) {
358                     if (this.isShared () || thisEntryState.getLanguageCode ().equals (
359                             curLanguageCode)) {
360                         foundInActive = true;
361                         break;
362                     }
363                 }
364             }
365             if (this.isShared ()) {
366                 // switch lang to shared
367
curLanguageCode = ContentObject.SHARED_LANGUAGE;
368             }
369             EntrySaveRequest saveRequest = new EntrySaveRequest (user,
370                     curLanguageCode);
371             if (foundInActive) {
372                 // FIXME : Site language deletion issue :
373
// The only case in which we can mark for delete a SHARED lang entry ,
374
// is when the languageCode is SHARED, in other situation, we can't
375
// mark it for delete
376
if ( (this.isShared() && ContentField.SHARED_LANGUAGE.equals(languageCode))
377                      || !this.isShared() ){
378                     preSet(NO_VALUE, saveRequest);
379                 }
380             } else {
381                 Set tempLanguageCodes = new HashSet ();
382                 tempLanguageCodes.add (saveRequest.getLanguageCode ());
383                 deleteStagingEntries (tempLanguageCodes);
384             }
385         }
386
387         if (stateModified) {
388             stateModificationContext.popAllLanguages ();
389         }
390
391         notifyFieldUpdate();
392
393         // update the search index
394
ServicesRegistry.getInstance ().getJahiaSearchService ()
395                 .removeFieldFromSearchEngine (this.getSiteID (), this.getID (),
396                         ContentObjectEntryState.WORKFLOW_STATE_START_STAGING, languageCode);
397     }
398
399     /**
400      * This method should be called when the staged version has to be activated.
401      *
402      * @param JahiaSaveVersion containing the new VersionID
403      * @param jParams ParamBean needed to destroy page related data such as
404      * fields, sub pages, as well as generated JahiaEvents.
405      */

406     public synchronized ActivationTestResults activate (
407             Set languageCodes,
408             int newVersionID,
409             ParamBean jParams,
410             StateModificationContext stateModifContext) throws JahiaException {
411         ActivationTestResults activationResults = new ActivationTestResults ();
412
413         // check that we are not going to create a new version entry that already exist!
414
int activeVersionId = this.getActiveVersionID();
415         int deletedVersionId = this.getDeleteVersionID();
416         if ( activeVersionId == newVersionID
417              || deletedVersionId == newVersionID ){
418             // has been already activated! Do not activate again
419
// because we will create same save version date ( a corrupted situation )
420

421             // If this case happens, this means the activation process is running twice
422
// on content already activated with same activation time and this case
423
// should'nt happen or catched before reaching this point.
424
// FIXME : This issue was bring out with Document Listing template and LastPublishingDate metadata field
425
// When the Doc Listing page is activated before its childs Docs page, resulting in
426
// incomplete Doc container activation ( LastPublishingDate is going to be activated twice at same activation time )
427
activationResults.appendWarning("Field " + getID() +
428                     " is already activated with newVersionID=" + newVersionID);
429             return activationResults;
430         }
431
432
433         boolean versioningEnabled = ServicesRegistry.getInstance ().getJahiaVersionService ().isVersioningEnabled (jahiaID);
434         Vector stagedEntries = new Vector ();
435
436         boolean stateModified = false;
437         if (willBeCompletelyDeleted (null, languageCodes)) {
438             stateModified = true;
439             stateModifContext.pushAllLanguages (true);
440         }
441
442         Set activateLanguageCodes = new HashSet (languageCodes);
443         if (stateModifContext.isAllLanguages ()) {
444             activateLanguageCodes.addAll (getStagingLanguages (true));
445         }
446
447         activationResults.merge (
448                 isValidForActivation (activateLanguageCodes, jParams, stateModifContext));
449
450         /* FIXME NK : should we really abort the activation if there are only warning ?
451          * In case of page field (LINK) created initially without any link, the page field will fail activate
452          * because there is a warning stating that the Content Page is not valid for activation
453          */

454         /*
455         if ( activationResults.getStatus() != ActivationTestResults.COMPLETED_OPERATION_STATUS) {
456             return activationResults;
457         }
458         */

459         if (activationResults.getStatus () == ActivationTestResults.FAILED_OPERATION_STATUS) {
460             return activationResults;
461         }
462
463         // build the vector of only staged entries for the languages we want
464
// to activate.
465
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
466             ContentObjectEntryState entryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
467                     i);
468             if (entryState.isStaging ()) {
469
470                 if (entryState.getLanguageCode ().equals (ContentField.SHARED_LANGUAGE)
471                         || activateLanguageCodes.contains (entryState.getLanguageCode ())) {
472                     stagedEntries.add (entryState);
473                 }
474
475             }
476         }
477
478         /** @todo Remove all active entries that will be replaced in the search engine */
479
480         // FIXME NK :
481
// We first backup the active ( create an archive entry , then delete the active) before switching the staging to active,
482
// but we never guaranteed to switch the staging to active (done by calling changeEntryState )
483
// even thought we effectively deleted the active in db.
484
//
485

486         // we backup all active entries that will be replaced
487
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
488             ContentObjectEntryState actEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
489                     i);
490             if (actEntryState.isActive ()) {
491                 for (int j = 0; j < stagedEntries.size (); j++) {
492                     ContentObjectEntryState staEntryState = (ContentObjectEntryState) stagedEntries.get (
493                             j);
494                     if (actEntryState.getLanguageCode ().equals (
495                             staEntryState.getLanguageCode ())) {
496                         // ok appollo, we catched an active entry here! we handle this..
497
if (versioningEnabled) {
498
499                             ContentObjectEntryState backupEntryState = ContentFieldDB.getInstance ().backupDBValue (
500                                     this, actEntryState); // we backup the active version
501
// ok we backuped an entry of the field
502
if (backupEntryState != null) {
503                                 // if the entry state list is loaded, let's update it
504
if (versioningEntryStates != null) {
505                                     versioningEntryStates.add (backupEntryState);
506                                 }
507                                 // also we must call the changeEntryState because, like, we changed the version or something hehe..
508
this.changeEntryState (actEntryState, backupEntryState,
509                                         jParams, stateModifContext);
510                             }
511                         } else {
512                             // if versioning is not enabled, we delete the old value content!
513
this.deleteEntry (actEntryState);
514                         }
515                         // we then delete the active entry
516
ContentFieldDB.getInstance ().deleteDBValue (this, actEntryState);
517                         activeAndStagingEntryStates.remove (i);
518                         i -= 1;
519                     }
520                 }
521             }
522         }
523         // we activate every staged value
524
// we must take care of doing the "special trick" here, that is, if
525
// versionID = -1 in staged mode, it means we want to delete this entry of
526
// the field. So instead of activate it we must create a BACKUPED entry
527
// of this field with current version ID and versionStatus = -1
528
for (int i = 0; i < stagedEntries.size (); i++) {
529             ContentObjectEntryState staEntryState = (ContentObjectEntryState) stagedEntries.get (
530                     i);
531
532             ContentObjectEntryState newEntryState = null;
533             // if it's -1 -> delete flag
534
if (staEntryState.getVersionID () == -1) {
535                 // create a new entry state (deleted)
536
newEntryState = new ContentObjectEntryState (
537                         ContentObjectEntryState.WORKFLOW_STATE_VERSIONING_DELETED,
538                         newVersionID,
539                         staEntryState.getLanguageCode ());
540             } else {
541                 // create a new entry state
542
newEntryState = new ContentObjectEntryState (
543                         ContentObjectEntryState.WORKFLOW_STATE_ACTIVE,
544                         newVersionID,
545                         staEntryState.getLanguageCode ());
546             }
547
548             // check if we have to delete this field for REAL
549
if ((!versioningEnabled) && (newEntryState.getWorkflowState () <= 1)) {
550                 // yes, we must delete this field for real!
551
this.deleteEntry (staEntryState);
552             } else {
553                 // no, we must just change it/version it, etc...
554
// we call the field-type-specific changeEntryState
555
activationResults.merge (
556                         this.changeEntryState (staEntryState, newEntryState, jParams,
557                                 stateModifContext));
558
559                 /* FIXME NK : should we really abort the activation if there are only warning ?
560                  * In case of page field (LINK) created initially without any link, the page field will fail activate
561                  * because there is a warning stating that the Content Page is not valid for activation
562                  */

563                 /*
564                 if (activationResults.getStatus() == ActivationTestResults.COMPLETED_OPERATION_STATUS) {
565                 */

566                 if (activationResults.getStatus () != ActivationTestResults.FAILED_OPERATION_STATUS) {
567                     // update staged entry in DB into active or deleted entry state
568
ContentFieldDB.getInstance ().changeDBEntryState (this, staEntryState, newEntryState);
569                 } else {
570                     if (stateModified) {
571                         stateModifContext.popAllLanguages ();
572                     }
573                     return activationResults;
574                 }
575             }
576
577             // we remove the staged version from the tables
578
// String thisValue = getDBValue(staVerInfo); // let's not do that, we never know...
579
activeAndStagingEntryStates.remove (staEntryState); // remove it from the vector
580
loadedDBValues.remove (staEntryState); // remove it from the hashtable
581

582             // we add the new active version to the tables
583
// if active-staged version
584
if (newEntryState.getWorkflowState () >= 1) {
585                 activeAndStagingEntryStates.add (newEntryState); // add it to the vector
586
} else {
587                 // versioning version
588
if ((versioningEnabled) && (versioningEntryStates != null)) {
589                     versioningEntryStates.add (newEntryState);
590                 }
591             }
592 // loadedDBValues.put(newVerInfo, thisValue); // add it to the hashtable
593
}
594
595         // check if an active or staged version still exist, and if not, and
596
// if versioning is not enabled, it means the field is REALLY deleted !
597
// so we remove it's ACL etc...
598
if ((activeAndStagingEntryStates.size () == 0) &&
599                 (!versioningEnabled)) {
600             // true delete...
601
ContentFieldTools.getInstance ().purgeFieldData (this);
602         }
603
604         /** Remove field from search engine */
605         ServicesRegistry.getInstance ()
606                 .getJahiaSearchService ().removeFieldFromSearchEngine (this);
607
608         if (!this.willBeCompletelyDeleted (null, null)) {
609             /** Update search engine */
610             ServicesRegistry.getInstance ()
611                     .getJahiaSearchService ().indexField (this.getID (), false,
612                                                           jParams, true);
613         }
614
615         if (stateModified) {
616             stateModifContext.popAllLanguages ();
617         }
618
619         // let's inform the cache server that we have updated this object,
620
// so that other nodes in the cluster can update their values.
621
ContentFieldTools.getInstance ().updateCache(this);
622
623         return activationResults;
624     }
625
626     /**
627      * This method implements tests for activation validity. Basically what
628      * must be done here is test if the field is ready to be activated. This
629      * include checking if it has dependencies on content that hasn't been
630      * activated yet, or missing languages for mandatory languages.
631      * This method calls the isContentValidForActivation method that is
632      * polymorphic and implemented for each class of ContentField.
633      *
634      * @return an ActivationTestResults object that indicates if the activation
635      * would fail, only partially succeed (for exemple because a page field
636      * depends on a pages that hasn't been validated), or successfully completes.
637      * There are two types of messages returned by the test results : errors,
638      * and warnings. Errors are associated with a test failure, while warnings
639      * are associated with partial completion of the activation.
640      */

641     public ActivationTestResults isValidForActivation (
642             Set languageCodes,
643             ParamBean jParams,
644             StateModificationContext stateModifContext)
645             throws JahiaException {
646         ActivationTestResults activationTestResults = new ActivationTestResults ();
647
648         int containerId = this.getContainerID();
649         ContentContainer contentContainer = null;
650         if ( containerId > 0 ){
651             contentContainer = ContentContainer.getContainer(containerId);
652         }
653
654         boolean deleted = this.isDeleted(activeAndStagingEntryStates);
655         boolean markedForDelete = this.isMarkedForDelete();
656         
657         if ( ((contentContainer == null) && !(deleted || markedForDelete)) ||
658              ((contentContainer != null) && !((contentContainer.isMarkedForDelete() || contentContainer.isDeleted(contentContainer.getActiveAndStagingEntryStates())))
659               && !(deleted || markedForDelete) ) ){
660             
661             // first we must test if we have all the mandatory languages in our
662
// field
663
JahiaSite theSite = jParams.getSite();
664
665             // first let's check if all the languages have unitialized values,
666
// in which case we do not need to do mandatory language checks.
667
boolean allEntriesUninitialized = true;
668             Iterator activeAndStagingEntryEnum = activeAndStagingEntryStates.iterator();
669             Map languagesInitialized = new HashMap(32);
670             while (activeAndStagingEntryEnum.hasNext()) {
671                 ContentObjectEntryState curEntryState = (ContentObjectEntryState)
672                 activeAndStagingEntryEnum.next();
673                 if (isEntryInitialized(curEntryState)) {
674                     allEntriesUninitialized = false;
675                     languagesInitialized.put(curEntryState.getLanguageCode(),Boolean.TRUE);
676                 }
677             }
678
679             if (!markedForDelete && (!allEntriesUninitialized)) {
680                 Vector languageSettings = theSite.getLanguageSettings();
681                 Enumeration languageSettingsEnum = languageSettings.elements();
682                 while (languageSettingsEnum.hasMoreElements()) {
683                     SiteLanguageSettings curSettings = (SiteLanguageSettings)
684                         languageSettingsEnum.nextElement();
685                     if (curSettings.isMandatory()) {
686                         // we found a mandatory language, let's check that there at
687
// least an active or a staged entry for this field.
688
boolean foundLanguage = false;
689                         if(languagesInitialized.containsKey(curSettings.getCode()) ||
690                                 languagesInitialized.containsKey(ContentField.SHARED_LANGUAGE))
691                              foundLanguage = true;
692
693                         if (!foundLanguage) {
694                             activationTestResults.setStatus(
695                                 ActivationTestResults.FAILED_OPERATION_STATUS);
696                             try {
697                                 JahiaFieldDefinition fieldDefinition = JahiaFieldDefinitionsRegistry.getInstance().getDefinition(getFieldDefID());
698                                 String JavaDoc fieldTitle = null;
699                                 if (fieldDefinition != null) {
700                                     try {
701                                         fieldTitle = fieldDefinition.getTitle(
702                                             getPage().getPageTemplate(jParams.
703                                             getEntryLoadRequest()).getID(),
704                                             jParams.getLocale());
705                                     } catch (NullPointerException JavaDoc npe) {
706                                         // if we can't solve the title, let's
707
// just do nothing.
708
}
709                                 }
710                                 final EngineMessage msg = new EngineMessage(
711                                         "org.jahia.services.fields.ContentField.mandatoryLangMissingError",
712                                         fieldTitle);
713                                 IsValidForActivationResults activationResults = new IsValidForActivationResults(
714                                     ContentFieldKey.FIELD_TYPE, getID(),
715                                     curSettings.getCode(), msg);
716                                 activationTestResults.appendError(
717                                     activationResults);
718                             } catch (ClassNotFoundException JavaDoc cnfe) {
719                                 logger.debug(
720                                     "Error while creating activation test node result",
721                                     cnfe);
722                             }
723                         }
724
725                     }
726                 }
727             }
728         }
729
730         activationTestResults.merge (
731                 isContentValidForActivation (languageCodes, jParams, stateModifContext));
732
733         return activationTestResults;
734     }
735
736     /**
737      * This method is used to check if the field has been initialized for
738      * a specific entry state. This is mostly used to figure out whether we
739      * must perform mandatory language check. If all entry states are un-
740      * initialized, we must not perform mandatory language checks.
741      *
742      * You will want to redefine this method in subclasses in order to check
743      * for other empty value markers.
744      *
745      * @param curEntryState ContentObjectEntryState the entry state for which
746      * to check if the entry's value has been initialized.
747      * @throws JahiaException
748      * @return boolean true if the field has been initialized with a value,
749      * false otherwise.
750      */

751     protected boolean isEntryInitialized (ContentObjectEntryState curEntryState)
752         throws JahiaException {
753         String JavaDoc entryValue = getValue(curEntryState);
754         if (entryValue == null) {
755             return false;
756         }
757         if (!entryValue.equals("") &&
758             !entryValue.equals("<empty>")) {
759             return true;
760         }
761         return false;
762     }
763
764     /**
765      * This method removes all the data related to the staging mode of this
766      * field, effectively "undoing" all the changes and returning to the
767      * active values.
768      *
769      * @throws JahiaException in the case there are errors accessing the
770      * persistant storage system.
771      */

772     public synchronized void undoStaging (ParamBean jParams)
773             throws JahiaException {
774
775         // first we construct a vector of all the staging versions
776
Vector stagedEntryStates = new Vector ();
777         for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
778             ContentObjectEntryState entryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
779                     i);
780             // ok huston, we have a staged version here, let's advise...
781
if (entryState.isStaging ()) {
782                 stagedEntryStates.add (entryState);
783             }
784         }
785
786         // now let's use that vector to destroy all staged versions
787
for (int i = 0; i < stagedEntryStates.size (); i++) {
788             ContentObjectEntryState curEntryState = (ContentObjectEntryState) stagedEntryStates.get (
789                     i);
790
791             // first we call the field to destroy it's related data
792
this.deleteEntry (curEntryState);
793
794             // now let's delete the database value
795
ContentFieldDB.getInstance ().deleteDBValue (this, curEntryState);
796
797             // finally let's remove it from the internal tables.
798
activeAndStagingEntryStates.remove (curEntryState); // remove it from the vector
799
loadedDBValues.remove (curEntryState); // remove it from the hashtable
800
}
801
802         notifyFieldUpdate();
803
804         if ( this.hasStagingEntries() || this.hasActiveEntries() ){
805             // let's inform the cache server that we have updated this object,
806
// so that other nodes in the cluster can update their values.
807
ContentFieldTools.getInstance ().updateCache(this);
808
809             /** Remove field from search engine */
810             ServicesRegistry.getInstance ()
811                     .getJahiaSearchService ().removeFieldFromSearchEngine (this);
812             // update the search index
813
ServicesRegistry.getInstance().getJahiaSearchService()
814                 .indexField(this.getID(),false,Jahia.getThreadParamBean(), true);
815         } else {
816             ServicesRegistry.getInstance ()
817                     .getJahiaSearchService ().removeFieldFromSearchEngine (this);
818             ContentFieldTools.getInstance ().removeFromCache(getID());
819         }
820     }
821
822     /**
823      * This method purges the fields from the database, removing all versions,
824      * all workflow states, everything !
825      *
826      * @throws JahiaException in case an error occurs while executing the
827      * purging operations.
828      */

829     public synchronized void purge ()
830             throws JahiaException {
831         purgeContent ();
832
833         JahiaBaseACL localAcl = getACL ();
834         localAcl.delete ();
835
836         this.activeAndStagingEntryStates.clear ();
837         this.loadedDBValues.clear ();
838         if (this.versioningEntryStates != null) {
839             this.versioningEntryStates.clear ();
840         }
841
842         ContentFieldDB.getInstance ().purgeField (getID ());
843     }
844
845     /**
846      * This is the getValue method that should be implemented by the different
847      * ContentFields. It is called by the public getValue() method defined in this
848      * class, which handles entry resolving
849      * This method should call getDBValue to get the DBValue
850      */

851     public abstract String JavaDoc getValue (ParamBean jParams,
852                                         ContentObjectEntryState entryState)
853             throws JahiaException;
854
855     /**
856      * This is the getValue method that should be implemented by the different
857      * ContentFields. It is called by the public getValue() method defined in this
858      * class, which handles entry resolving
859      * This method should call getDBValue to get the DBValue
860      *
861      * @param jParams the jParam containing the loadVersion and locales
862      */

863     public abstract String JavaDoc getValueForSearch (ParamBean jParams,
864                                               ContentObjectEntryState entryState)
865             throws JahiaException;
866
867     /**
868      * This method is called when there is a workflow state change
869      * Such as staged mode -> active mode (validation), active -> inactive (for versioning)
870      * and also staged mode -> other staged mode (workflow)
871      * This method should not write/change the DBValue, the service handles that.
872      *
873      * @param fromEntryState the entry state that is currently was in the database
874      * @param toEntryState the entry state that will be written to the database
875      * @param jParams ParamBean object used to get information about the user
876      * doing the request, the current locale, etc...
877      *
878      * @return null if the entry state change wasn't an activation, otherwise it
879      * returns an object that contains the status of the activation (whether
880      * successfull, partial or failed, as well as messages describing the
881      * warnings during the activation process)
882      */

883     protected abstract ActivationTestResults changeEntryState (
884             ContentObjectEntryState fromEntryState,
885             ContentObjectEntryState toEntryState,
886             ParamBean jParams,
887             StateModificationContext stateModifContext) throws JahiaException;
888
889     /**
890      * This method implements tests for content activation validity. Basically
891      * what must be done here is test if the field content is ready to be
892      * activated. This include checking if it has dependencies on content that
893      * hasn't been activated yet, or missing languages for mandatory languages.
894      *
895      * @return an ActivationTestResults object that indicates if the activation
896      * would fail, only partially succeed (for exemple because a page field
897      * depends on a pages that hasn't been validated), or successfully completes.
898      * There are two types of messages returned by the test results : errors,
899      * and warnings. Errors are associated with a test failure, while warnings
900      * are associated with partial completion of the activation.
901      */

902     protected abstract ActivationTestResults isContentValidForActivation (
903             Set languageCodes,
904             ParamBean jParams, StateModificationContext stateModifContext)
905             throws JahiaException;
906
907
908     /**
909      * This is called on all content fields to have them serialized only their
910      * specific part. The actual field metadata seriliazing is handled by the
911      * ContentField class. This method is called multiple times per field
912      * according to the workflow state, languages and versioning entries we
913      * want to serialize.
914      *
915      * @param xmlWriter the XML writer object in which to write the XML output
916      * @param xmlSerializationOptions options used to activate/bypass certain
917      * output of elements.
918      * @param entryState the ContentFieldEntryState for which to generate the
919      * XML export.
920      * @param paramBean specifies context of serialization, such as current
921      * user, current request parameters, entry load request, URL generation
922      * information such as ServerName, ServerPort, ContextPath, etc... URL
923      * generation is an important part of XML serialization and this is why
924      * we pass this parameter down, as well as user rights checking.
925      *
926      * @throws IOException in case there was an error writing to the Writer
927      * output object.
928      */

929     protected abstract void serializeContentToXML (XmlWriter xmlWriter,
930                                                    XMLSerializationOptions xmlSerializationOptions,
931                                                    ContentObjectEntryState entryState,
932                                                    ParamBean paramBean)
933             throws IOException JavaDoc;
934
935     /**
936      * Called at the beginning of a purge operation and must remove all the
937      * field related data, recursively if necessary.
938      * throws JahiaException if an exception is thrown during the purging of
939      * the fields content.
940      */

941     protected void purgeContent () throws JahiaException {
942         // default implementation does nothing.
943
}
944
945     /**
946      * Returns the workflow state of all the languages contained in this fields
947      * content object. This is used for example to get the internal state of
948      * page objects.
949      * In this class we provide a default implementation as most fields that
950      * are not shared will not need to do this. Ideally we might want to have
951      * seperate interfaces for shared and non-shared fields or something like
952      * this.
953      * This returns the state of both the active and staged languages, the
954      * staging version taking priority over the active for a given language.
955      *
956      * @param entryState the field entry state for which to retrieve the
957      * content language states.
958      *
959      * @return an HashMap that contains the language code String as the key,
960      * and the current workflow state of the language is the value
961      */

962     protected Map getContentLanguageStates (ContentObjectEntryState entryState) {
963         return null;
964     }
965
966     /**
967      * Called when marking a language for deletion on a field. This is done
968      * first to allow field implementation to provide a custom behavior when
969      * marking fields for deletion. It isn't abstract because most fields will
970      * not need to redefine this method.
971      *
972      * @param user the user performing the operation
973      * @param languageCode the language to mark for deletion.
974      * @param stateModifContext used to detect loops in deletion marking.
975      *
976      * @throws JahiaException in the case there was an error processing the
977      * marking of the content.
978      */

979     protected void markContentLanguageForDeletion (JahiaUser user,
980                                                    String JavaDoc languageCode,
981                                                    StateModificationContext stateModifContext)
982             throws JahiaException {
983         return;
984     }
985
986     /**
987      * Is this kind of field shared (i.e. not one version for each language, but one version for every language)
988      */

989     public abstract boolean isShared ();
990
991     /**
992      * Creates a ContentField value in RAM, with no content
993      * This method should be called to create a field, and the field content should be set right
994      * after that! This method allocates a new AclID and fieldID so don't play with it if you
995      * don't have a firm intention to create a field ;)
996      *
997      * @param siteID the site identifier to which this new field belongs
998      * @param pageID the page Id to which this new fields belongs
999      * @param containerID the containerID to which this new fields belongs
1000     * @param fieldDefID the identifier for the field definition
1001     * @param typeField an integer specifying the field type (see ContentFieldTypes)
1002     * @param connectType the type of connection for the field, legacy method
1003     * for storing big text editor type and previously to specify if the field
1004     * was local or remote (datasourcing)
1005     * @param parentAclid the acl ID of the parent object
1006     * @param aclID the ACL for this new field if we already have one, or 0 to
1007     * create a new ACL for the field
1008     *
1009     * @return the created content field. Do a set on it right after, if no set is done, the
1010     * field will never be written to the DB ! (but its acl won't be deleted)
1011     */

1012    protected static synchronized ContentField createField (int siteID,
1013                                                            int pageID, int containerID,
1014                                                            int fieldDefID,
1015                                                            int typeField, int connectType,
1016                                                            int parentAclID, int aclID)
1017            throws JahiaException {
1018        return ContentFieldTools.getInstance ().createField (siteID, pageID,
1019                containerID, fieldDefID, typeField, connectType, parentAclID,
1020                aclID);
1021    }
1022
1023    /**
1024     * Get a ContentField from its ID
1025     *
1026     * @throws JahiaException If the field doesn't exist, or there's a DB error
1027     */

1028    public static ContentField getField (int fieldID)
1029            throws JahiaException {
1030        return ContentFieldTools.getInstance ().getField (fieldID);
1031    }
1032
1033    /**
1034     * Get a ContentField from its ID
1035     *
1036     * @param fieldID
1037     * @param forceloadFromDB
1038     *
1039     * @return
1040     *
1041     * @throws JahiaException
1042     */

1043    public static ContentField getField (int fieldID, boolean forceLoadFromDB)
1044            throws JahiaException {
1045        return ContentFieldTools.getInstance ().getField (fieldID, forceLoadFromDB);
1046    }
1047
1048    /**
1049     * Get a ContentField from its ID from cache only
1050     *
1051     * @throws JahiaException If the field doesn't exist, or there's a DB error
1052     */

1053    public static ContentField getFieldFromCacheOnly (int fieldID)
1054            throws JahiaException {
1055        return ContentFieldTools.getInstance ().getContentFieldFromCacheOnly(fieldID);
1056    }
1057    
1058    /**
1059     * Preloads all the active or staged fields for a given page into the
1060     * fields cache.
1061     *
1062     * @param pageID the page ID for which to preload all the content fields
1063     *
1064     * @throws JahiaException thrown if there was an error while loading the
1065     * fields from the database.
1066     */

1067    public static synchronized void preloadActiveOrStagedFieldsByPageID (int pageID)
1068            throws JahiaException {
1069        ContentFieldTools.getInstance ().preloadActiveOrStagedFieldsByPageID (pageID);
1070    }
1071
1072    /**
1073     * Removes a field from the cache if it was present in the cache. If not,
1074     * this does nothing.
1075     *
1076     * @param fieldID the identifier for the field to try to remove from the
1077     * cache.
1078     */

1079    public static synchronized void removeFromCache (int fieldID) {
1080        ContentFieldTools.getInstance ().removeFromCache (fieldID);
1081    }
1082
1083
1084    /**
1085     * Preloads all the active or staged fields for a given container into the
1086     * field cache.
1087     *
1088     * @param containerID the container ID for which to preload all the content fields
1089     *
1090     * @throws JahiaException thrown if there was an error while loading the
1091     * fields from the database.
1092     */

1093    public static synchronized void preloadActiveOrStagedFieldsByContainerID (int containerID)
1094            throws JahiaException {
1095        ContentFieldTools.getInstance ().preloadActiveOrStagedFieldsByContainerID (containerID);
1096    }
1097
1098    /**
1099     * Delete a ContentField from its ID
1100     * (This is a user request. In fact, it just puts a "delete flag" on every language of
1101     * The field, and the field will really be delete when the page is staging-validated
1102     * and versioning is disabled)
1103     *
1104     * @param user the user performing the operation
1105     * @param stateModifContext used to detect loops in deletion marking.
1106     *
1107     * @throws JahiaException If the field doesn't exist, or there's a DB error
1108     */

1109    public static synchronized void markFieldForDeletion (int fieldID,
1110                                                          JahiaUser user,
1111                                                          StateModificationContext stateModifContext)
1112            throws JahiaException {
1113        ContentField theField = ContentField.getField (fieldID); // exception is thrown if the field stateModifContextdoesn't exist
1114
synchronized (theField) {
1115
1116            ContentFieldTools.getInstance ().markFieldForDeletion (theField,
1117                user,
1118                theField.activeAndStagingEntryStates,
1119                stateModifContext);
1120
1121            // let's inform the cache server that we have updated this object,
1122
// so that other nodes in the cluster can update their values.
1123
ContentFieldTools.getInstance().updateCache(theField);
1124        }
1125    }
1126
1127    /**
1128     * Method that HAVE to be called at the beginning of every field setter.
1129     * It updates the DBValue into the DB
1130     *
1131     * @param saveRequest only used to know which language we have to store.
1132     *
1133     * @return EntryState of the value stored into the DB, or null if we
1134     * couldn't delete the field because it doesn't have an active entry.
1135     */

1136    protected ContentObjectEntryState preSet (String JavaDoc newDBValue, EntrySaveRequest saveRequest)
1137            throws JahiaException {
1138        int currentStatus = ContentObjectEntryState.WORKFLOW_STATE_START_STAGING;
1139        // let's see if a currentVerInfo exist
1140
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1141            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1142                    i);
1143            if (thisEntryState.isStaging ()) {
1144                currentStatus = thisEntryState.getWorkflowState ();
1145                break;
1146            }
1147        }
1148
1149        int currentVersionID = 0; // always 0 in staging
1150
if (newDBValue.equals (NO_VALUE)) {
1151            currentVersionID = -1;
1152            // let's lookup the previous value.
1153
ContentObjectEntryState oldEntryState = null;
1154            // let's first look in the active values.
1155
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1156                ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1157                        i);
1158                if ((!thisEntryState.isStaging ()) &&
1159                        (thisEntryState.getLanguageCode ().equals (
1160                                saveRequest.getLanguageCode ()))) {
1161                    oldEntryState = thisEntryState;
1162                    break;
1163                }
1164            }
1165            if (oldEntryState == null) {
1166                for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1167                    ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1168                            i);
1169                    if ((thisEntryState.isStaging ()) &&
1170                            (thisEntryState.getLanguageCode ().equals (
1171                                    saveRequest.getLanguageCode ()))) {
1172                        oldEntryState = thisEntryState;
1173                        break;
1174                    }
1175                }
1176            }
1177            // if found we assign the previous value.
1178
if (oldEntryState != null) {
1179                newDBValue = getDBValue (oldEntryState);
1180            }
1181        }
1182        String JavaDoc currentLanguage = saveRequest.getLanguageCode ();
1183        ContentObjectEntryState newEntryState = new ContentObjectEntryState (currentStatus,
1184                currentVersionID, currentLanguage);
1185        // end get new version info
1186

1187        ContentObjectEntryState currentEntryState = null; // it will contain the current versionInfo if it exists in DB
1188
// let's see if a current entry exists in staging that corresponds to our new value
1189
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1190            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1191                    i);
1192            if ((thisEntryState.isStaging ()) &&
1193                    (thisEntryState.getLanguageCode ().equals (saveRequest.getLanguageCode ()))) {
1194                currentEntryState = thisEntryState;
1195                break;
1196            }
1197        }
1198
1199        // remove old value and version info from the memory tables if it existed
1200
if (currentEntryState != null) {
1201            // remove the version from the activeAndStagingVersionInfo
1202
activeAndStagingEntryStates.remove (currentEntryState);
1203            // we must remove it from the hashtable to change the version ID
1204
loadedDBValues.remove (currentEntryState);
1205        }
1206
1207        // we put it back in the hashtable
1208
loadedDBValues.put (newEntryState, newDBValue);
1209        // add the new version from the activeAndStagingVersionInfo
1210
activeAndStagingEntryStates.add (newEntryState);
1211
1212        // if the version is new one we create a new EMPTY field version
1213
if (currentEntryState == null) {
1214            ContentFieldDB.getInstance ().createDBValue (this, newEntryState);
1215        }
1216
1217        // we update it with the new value
1218
ContentFieldDB.getInstance ().updateDBValue (this, newEntryState, getDBValue (newEntryState));
1219
1220        // let's inform the cache server that we have updated this object,
1221
// so that other nodes in the cluster can update their values.
1222
ContentFieldTools.getInstance ().updateCache(this);
1223
1224        return newEntryState;
1225    }
1226
1227    /**
1228     * @return the language
1229     */

1230    protected String JavaDoc getSetLanguageCode (ParamBean jParams)
1231            throws JahiaException {
1232        if (isShared ()) {
1233            return (SHARED_LANGUAGE);
1234        } else {
1235            return jParams.getLocale ().toString (); // can crash if there is no language in the session (!?!)
1236
}
1237    }
1238
1239    /**
1240     * Gets the value of the field that is contained in the value_jahia_fields_data
1241     * column of the database, for a given VersionInfo. Loads it if not already
1242     * loaded.
1243     *
1244     * @throws JahiaException If field/version doesn't exist or there is a database error
1245     */

1246    protected String JavaDoc getDBValue (EntryStateable entryState) throws JahiaException {
1247        // We ensure to check if this content object is Shared or not even though
1248
// the entryState is provided with a specific language ( en, fr, ... )
1249
String JavaDoc languageCode = entryState.getLanguageCode ();
1250        if (this.isShared ()) {
1251            languageCode = ContentObject.SHARED_LANGUAGE;
1252        }
1253        ContentObjectEntryState objectEntryState = new ContentObjectEntryState (
1254                entryState.getWorkflowState (), entryState.getVersionID (),
1255                languageCode);
1256        String JavaDoc theResult = (String JavaDoc) loadedDBValues.get (objectEntryState);
1257        // not already in hashmap, let's put it in !
1258
if (theResult == null) {
1259            theResult = ContentFieldDB.getInstance ().loadDBValue (this, objectEntryState);
1260            loadedDBValues.put (objectEntryState, theResult);
1261        }
1262        return theResult;
1263    }
1264
1265    /**
1266     * Gets all the values of the field that are contained in the
1267     * value_jahia_fields_data column of the database. This does not load up
1268     * into the caches since the data might be quite large, and will probably
1269     * not be reused often.
1270     *
1271     * @return a Map containing as the key a ContentFieldEntryState object, and
1272     * as value the String containing the value for the field.
1273     *
1274     * @throws JahiaException If field/version doesn't exist or there is a
1275     * database error
1276     */

1277    protected Map getAllDBValues () throws JahiaException {
1278        Map theResult = ContentFieldDB.getInstance ().loadAllDBValues (this);
1279        return theResult;
1280    }
1281
1282
1283    /**
1284     * Returns true if the field has active entries.
1285     *
1286     * @return true if the field has active entries.
1287     */

1288    public boolean hasActiveEntries () {
1289        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1290            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1291                    i);
1292            if (thisEntryState.isActive ()) {
1293                return true;
1294            }
1295        }
1296        return false;
1297    }
1298
1299    /**
1300     * Returns true if the field has staging entries.
1301     */

1302    public boolean hasStagingEntries () {
1303        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1304            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1305                    i);
1306            if (thisEntryState.isStaging ()) {
1307                return true;
1308            }
1309        }
1310        return false;
1311    }
1312
1313    /**
1314     * Returns true if the field has staging entry of given language.
1315     *
1316     * @return true if the field has staging entry of given language.
1317     */

1318    public boolean hasStagingEntry (String JavaDoc languageCode) {
1319        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1320            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1321                    i);
1322            if (thisEntryState.isStaging () && thisEntryState.getLanguageCode ().equals (
1323                    languageCode)) {
1324                return true;
1325            }
1326        }
1327        return false;
1328    }
1329
1330    /**
1331     * Returns true if the field has staging entry of given language.
1332     *
1333     * @return true if the field has staging entry of given language.
1334     */

1335    public boolean hasStagingEntryIgnoreLanguageCase (String JavaDoc languageCode) {
1336        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1337            ContentObjectEntryState thisEntryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1338                    i);
1339            if (thisEntryState.isStaging ()
1340                && (ContentObject.SHARED_LANGUAGE.equalsIgnoreCase(thisEntryState.getLanguageCode ())
1341                || thisEntryState.getLanguageCode ().equalsIgnoreCase(languageCode))) {
1342                return true;
1343            }
1344        }
1345        return false;
1346    }
1347
1348    /**
1349     * Returns the workflow state of all the languages contained in this field.
1350     * This returns the state of both the active and staged languages, the
1351     * staging version taking priority over the active for a given language.
1352     *
1353     * @return an HashMap that contains the language code String as the key,
1354     * and the current workflow state of the language is the value
1355     */

1356    public Map getLanguagesStates () {
1357
1358        int size = activeAndStagingEntryStates.size ();
1359        Map languageStates = new HashMap ();
1360
1361        // first let's get all the active languages in the map.
1362
for (int i = 0; i < size; i++) {
1363            ContentObjectEntryState entryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1364                i);
1365
1366            // we only return the highest valued workflow stage between active and staging.
1367
Integer JavaDoc curWorkflowState = (Integer JavaDoc) languageStates.get(entryState.getLanguageCode ());
1368            if (curWorkflowState == null || curWorkflowState.intValue() < entryState.getWorkflowState ()) {
1369                languageStates.put(entryState.getLanguageCode(),
1370                                   new Integer JavaDoc(entryState.getWorkflowState()));
1371            }
1372
1373            // now let's ask all the different field implementations to provide
1374
// their internal language status.
1375
Map contentLanguageStates = getContentLanguageStates (entryState);
1376            if (contentLanguageStates != null) {
1377                Iterator contentLanguageKeysIter = contentLanguageStates.keySet().
1378                    iterator();
1379                while (contentLanguageKeysIter.hasNext()) {
1380                    String JavaDoc curLanguageKey = (String JavaDoc) contentLanguageKeysIter.
1381                                            next();
1382                    languageStates.put(curLanguageKey,
1383                                       contentLanguageStates.get(curLanguageKey));
1384                }
1385            }
1386        }
1387
1388        return languageStates;
1389    }
1390
1391
1392    /**
1393     * Get an enumeration of active and staged entry state.
1394     */

1395    public SortedSet getActiveAndStagingEntryStates () {
1396        SortedSet entries = new TreeSet ();
1397        entries.addAll (activeAndStagingEntryStates);
1398        return entries;
1399    }
1400
1401    /**
1402     * Get an enumeration of all the entrystates. Loads the versioning entry
1403     * states from the database if they weren't available.
1404     *
1405     * @return a SortedSet of ContentFieldEntryState objects.
1406     */

1407    public SortedSet getEntryStates ()
1408            throws JahiaException {
1409        if (versioningEntryStates == null) {
1410            versioningEntryStates = ContentFieldDB.getInstance ().loadOldEntryStates (this);
1411        }
1412        SortedSet entryStates = new TreeSet ();
1413        entryStates.addAll (activeAndStagingEntryStates);
1414        entryStates.addAll (versioningEntryStates);
1415        return entryStates;
1416    }
1417
1418    /**
1419     * Allows to set the workflow state of a set of languages in this content
1420     * object.
1421     *
1422     * @param languageCodes a set of language codes for which to set the new
1423     * workflow state
1424     * @param newWorkflowState the integer value of the new workflow state
1425     * @param jParams a ParamBean object useful for some treatments, notably
1426     * when we address ContentPageFields
1427     * @param withSubPages if set to true, sub pages inside content page fields
1428     * will also be asked to change their state.
1429     *
1430     * @throws JahiaException raised if we have trouble storing the new
1431     * workflow state in persistant storage.
1432     */

1433    public void setWorkflowState (Set languageCodes,
1434                                  int newWorkflowState,
1435                                  ParamBean jParams,
1436                                  StateModificationContext stateModifContext)
1437            throws JahiaException {
1438
1439        Vector newActiveAndStagingEntryStates = new Vector ();
1440        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1441            ContentObjectEntryState entryState = (ContentObjectEntryState) activeAndStagingEntryStates.get (
1442                    i);
1443            // ok huston, we have a staged entry here, let's advise...
1444
if (entryState.isStaging () &&
1445                    (ContentObject.SHARED_LANGUAGE.equals (entryState.getLanguageCode ())
1446                    || languageCodes.contains (entryState.getLanguageCode ())) &&
1447                    (newWorkflowState >= ContentObjectEntryState.WORKFLOW_STATE_START_STAGING)) {
1448                // now we must updates both the internal data and the
1449
// database to reflect these changes.
1450
ContentObjectEntryState newEntryState =
1451                        new ContentObjectEntryState (newWorkflowState,
1452                                entryState.getVersionID (),
1453                                entryState.getLanguageCode ());
1454
1455                this.changeEntryState (entryState, newEntryState, jParams, stateModifContext);
1456
1457                ContentFieldDB.getInstance ().changeDBEntryState (this, entryState, newEntryState);
1458
1459                newActiveAndStagingEntryStates.add (newEntryState);
1460
1461            } else {
1462                newActiveAndStagingEntryStates.add (entryState);
1463            }
1464
1465        }
1466        activeAndStagingEntryStates = newActiveAndStagingEntryStates;
1467
1468        // let's inform the cache server that we have updated this object,
1469
// so that other nodes in the cluster can update their values.
1470
ContentFieldTools.getInstance ().updateCache(this);
1471
1472    }
1473
1474    /**
1475     * Change the Field Type. i.e from undefined to another type.
1476     *
1477     * @param fieldType the new field type.
1478     * @param fieldValue the field value needed to create a new Staging entry of the new type.
1479     * @param ParamBean the param Bean.
1480     * @param entrySaveRequest
1481     *
1482     * @return a new instance of ContentField of new Field Type.
1483     *
1484     * @author NK
1485     */

1486    public synchronized ContentField changeType (int aFieldType,
1487                                                 String JavaDoc fieldValue,
1488                                                 ParamBean jParams,
1489                                                 EntrySaveRequest entrySaveRequest)
1490            throws JahiaException {
1491        // 1. versioning active.
1492
// 2. delete staged.
1493
// 3. create a staged entry with the new type.
1494
// 4. return a new Instance of ContentField of new Type from DB.
1495

1496        boolean versioningEnabled = ServicesRegistry.getInstance ().getJahiaVersionService ().isVersioningEnabled (jahiaID);
1497        Set languageCodes = new HashSet ();
1498
1499        // 1. we backup all active entries that will be replaced
1500
for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1501            ContentObjectEntryState actEntryState =
1502                    (ContentObjectEntryState) activeAndStagingEntryStates.get (i);
1503            if (actEntryState.isStaging ()) {
1504                languageCodes.add (actEntryState.getLanguageCode ());
1505            }
1506            if (actEntryState.isActive ()) {
1507                // ok appollo, we catched an active entry here! we handle this..
1508
if (versioningEnabled) {
1509                    // we backup the active version
1510
ContentObjectEntryState backupEntryState = ContentFieldDB.getInstance ().backupDBValue (this,
1511                            actEntryState);
1512                    // ok we backuped an entry of the field
1513
if (backupEntryState != null) {
1514                        // if the entry state list is loaded, let's update it
1515
if (versioningEntryStates != null) {
1516                            versioningEntryStates.add (backupEntryState);
1517                        }
1518                        // also we must call the changeEntryState because, like, we changed the version or something hehe..
1519
StateModificationContext stateModifContext = new StateModificationContext (
1520                                new ContentFieldKey (getID ()), languageCodes);
1521                        stateModifContext.setDescendingInSubPages (false);
1522                        this.changeEntryState (actEntryState, backupEntryState, jParams,
1523                                stateModifContext);
1524                    }
1525                } else {
1526                    // if versioning is not enabled, we delete the old value content!
1527
this.deleteEntry (actEntryState);
1528                }
1529                // we then delete the active entry
1530
ContentFieldDB.getInstance ().deleteDBValue (this, actEntryState);
1531                activeAndStagingEntryStates.remove (i);
1532                i -= 1;
1533            }
1534        }
1535
1536        //2. Remove all staging entries
1537
this.deleteStagingEntries (languageCodes);
1538
1539        //3. Create a new staging entry of new field type
1540
this.fieldType = aFieldType;
1541        this.preSet (fieldValue, entrySaveRequest);
1542
1543        // 4. Return a new instance of correct type.
1544
return ContentFieldTools.getInstance ().getField (this.getID (), true);
1545    }
1546
1547    /**
1548     * Writes an XML serialization version of this content field, according to
1549     * the seriliazation options specified. This is very useful for exporting
1550     * Jahia content to external systems.
1551     *
1552     * @param xmlWriter the XML writer object in which to output the XML
1553     * exported data
1554     * @param xmlSerializationOptions the options that activate/deactivate
1555     * parts of the XML exported data.
1556     * @param paramBean specifies context of serialization, such as current
1557     * user, current request parameters, entry load request, URL generation
1558     * information such as ServerName, ServerPort, ContextPath, etc... URL
1559     * generation is an important part of XML serialization and this is why
1560     * we pass this parameter down, as well as user rights checking.
1561     *
1562     * @throws IOException upon error writing to the XMLWriter
1563     */

1564    public synchronized void serializeToXML (XmlWriter xmlWriter,
1565                                             XMLSerializationOptions xmlSerializationOptions,
1566                                             ParamBean paramBean) throws IOException JavaDoc {
1567
1568        // quick & dirty implementation that serializes the active and staging
1569
// entries.
1570
String JavaDoc name = null;
1571        try {
1572            JahiaFieldDefinition definition = JahiaFieldDefinitionsRegistry.getInstance ()
1573                    .getDefinition (getFieldDefID ());
1574            name = definition.getName ();
1575        } catch (JahiaException je) {
1576            logger.debug (
1577                    "Error while accessing field definition registry for field " + getID (),
1578                    je);
1579        }
1580
1581        xmlWriter.writeEntity ("contentField");
1582        if (name != null) {
1583            xmlWriter.writeAttribute ("name", name);
1584        }
1585        xmlWriter.writeAttribute ("type", this.getClass ().getName ());
1586        xmlWriter.writeAttribute ("shared", new Boolean JavaDoc (isShared ()).toString ());
1587
1588        /**
1589         * @todo here we should normally include code that will check for the
1590         * various options that can concern all field types, such as export of
1591         * ACL, different workflow states, etc...
1592         */

1593
1594        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1595            ContentObjectEntryState entryState =
1596                    (ContentObjectEntryState) activeAndStagingEntryStates.get (i);
1597
1598            xmlWriter.writeEntity ("entry").
1599                    writeAttribute ("language", entryState.getLanguageCode ()).
1600                    writeAttribute ("workflowState",
1601                            Integer.toString (entryState.getWorkflowState ())).
1602                    writeAttribute ("versionID", Long.toString (entryState.getVersionID ()));
1603
1604            serializeContentToXML (xmlWriter, xmlSerializationOptions, entryState, paramBean);
1605
1606            xmlWriter.endEntity ();
1607        }
1608
1609        xmlWriter.endEntity ();
1610
1611    }
1612
1613    /**
1614     * Delete permanently all the Staging entries
1615     */

1616    private void deleteStagingEntries (Set languageCodes)
1617            throws JahiaException {
1618        // for data integrity reasons we need to do this in two steps...
1619
ArrayList toBeRemoved = new ArrayList ();
1620        for (int i = 0; i < activeAndStagingEntryStates.size (); i++) {
1621            ContentObjectEntryState entryState =
1622                    (ContentObjectEntryState) activeAndStagingEntryStates.get (i);
1623            if (languageCodes.contains (entryState.getLanguageCode ())) {
1624                if (entryState.isStaging ()) {
1625                    toBeRemoved.add (entryState);
1626                }
1627            }
1628        }
1629
1630        Iterator toBeRemovedIter = toBeRemoved.iterator ();
1631        while (toBeRemovedIter.hasNext ()) {
1632            ContentObjectEntryState curEntryState = (ContentObjectEntryState) toBeRemovedIter.next ();
1633            deleteEntry (curEntryState);
1634        }
1635    }
1636
1637    /**
1638     * @throws JahiaException
1639     */

1640    private synchronized void loadVersioningEntryStates () throws JahiaException {
1641        if (this.versioningEntryStates == null) {
1642            this.versioningEntryStates = ContentFieldDB.getInstance ().loadOldEntryStates (this);
1643        }
1644    }
1645
1646    public ArrayList getChilds (JahiaUser user,
1647                                EntryLoadRequest loadRequest)
1648            throws JahiaException {
1649        // default implementation returns no children.
1650
return new ArrayList ();
1651    }
1652
1653    public ContentObject getParent (JahiaUser user,
1654                                    EntryLoadRequest loadRequest,
1655                                    String JavaDoc operationMode)
1656            throws JahiaException {
1657        if (getContainerID () > 0) {
1658            return ContentContainer.getContainer (getContainerID ());
1659        } else {
1660            return ContentPage.getPage (getPageID ());
1661        }
1662    }
1663
1664    private boolean hasEntry (EntryStateable entryState) {
1665        ContentObjectEntryState entryStateObject = new ContentObjectEntryState (entryState);
1666        if (entryStateObject.getWorkflowState () >= ContentObjectEntryState.WORKFLOW_STATE_ACTIVE) {
1667            int objectPos = activeAndStagingEntryStates.indexOf (entryStateObject);
1668            if (objectPos != -1) {
1669                return true;
1670            }
1671        } else {
1672            int objectPos = versioningEntryStates.indexOf (entryStateObject);
1673            if (objectPos != -1) {
1674                return true;
1675            }
1676
1677        }
1678        return false;
1679    }
1680
1681    /**
1682     * This method is called when a entry should be copied into a new entry
1683     * it is called when an old version -> active version move occurs
1684     * This method should not write/change the DBValue, the service handles that.
1685     *
1686     * @param fromEntryState the entry state that is currently was in the database
1687     * @param toEntryState the entry state that will be written to the database
1688     */

1689    protected void copyEntry (EntryStateable fromEntryState,
1690                              EntryStateable toEntryState)
1691            throws JahiaException {
1692
1693        ContentObjectEntryState fromE = new ContentObjectEntryState (fromEntryState);
1694        ContentObjectEntryState toE = new ContentObjectEntryState (toEntryState);
1695        if (this.isShared ()) {
1696            // swith to Shared lang
1697
fromE = new ContentObjectEntryState (fromEntryState.getWorkflowState (),
1698                    fromEntryState.getVersionID (), ContentObject.SHARED_LANGUAGE);
1699
1700            toE = new ContentObjectEntryState (toEntryState.getWorkflowState (),
1701                    toEntryState.getVersionID (), ContentObject.SHARED_LANGUAGE);
1702        }
1703
1704        if (hasEntry (toE)) {
1705            deleteEntry (toE);
1706        }
1707
1708        ContentFieldDB.getInstance ().copyEntry (getID (), fromE, toE);
1709        if (toE.getWorkflowState () < ContentObjectEntryState.WORKFLOW_STATE_ACTIVE) {
1710            versioningEntryStates.add (toE);
1711        } else {
1712            activeAndStagingEntryStates.add (toE);
1713        }
1714        loadedDBValues.put (toE, getDBValue (toE));
1715    }
1716
1717    /**
1718     * This method is called when an entry should be deleted for real.
1719     * It is called when a object is deleted, and versioning is disabled, or
1720     * when staging values are undone.
1721     * For a bigtext content fields for instance, this method should delete
1722     * the text file corresponding to the field entry
1723     *
1724     * @param deleteEntryState the entry state to delete
1725     * @param jParams ParamBean needed to destroy page related data such as
1726     * fields, sub pages, as well as generated JahiaEvents.
1727     */

1728    protected void deleteEntry (EntryStateable deleteEntryState)
1729            throws JahiaException {
1730        ContentFieldDB.getInstance ().deleteEntry (getID (), deleteEntryState);
1731        if (deleteEntryState.getWorkflowState () < ContentObjectEntryState.WORKFLOW_STATE_ACTIVE) {
1732            versioningEntryStates.remove (deleteEntryState);
1733        } else {
1734            activeAndStagingEntryStates.remove (deleteEntryState);
1735        }
1736        loadedDBValues.remove (deleteEntryState);
1737    }
1738
1739    /**
1740     * Call overrided restoreVersion then, reindex the field in search index
1741     * if it is not actually marked for delete.
1742     *
1743     * @param user
1744     * @param operationMode
1745     * @param entryState
1746     * @param removeMoreRecentActive
1747     * @param stateModificationContext
1748     * @return
1749     * @throws JahiaException
1750     */

1751    public RestoreVersionTestResults restoreVersion (JahiaUser user,
1752        String JavaDoc operationMode,
1753        ContentObjectEntryState entryState,
1754        boolean removeMoreRecentActive,
1755        RestoreVersionStateModificationContext stateModificationContext)
1756        throws JahiaException {
1757
1758        RestoreVersionTestResults result =
1759            super.restoreVersion(user,operationMode,entryState,
1760                             removeMoreRecentActive,stateModificationContext);
1761
1762        notifyFieldUpdate();
1763        // let's inform the cache server that we have updated this object,
1764
// so that other nodes in the cluster can update their values.
1765
ContentFieldTools.getInstance ().updateCache(this);
1766
1767        if (result.getStatus() !=
1768            RestoreVersionTestResults.FAILED_OPERATION_STATUS ){
1769            try {
1770                ServicesRegistry.getInstance().getJahiaSearchService()
1771                .removeFieldFromSearchEngine(this);
1772
1773                ServicesRegistry.getInstance().getJahiaSearchService()
1774                .indexField(this.getID(),false,Jahia.getThreadParamBean(), true);
1775            } catch ( Throwable JavaDoc t ){
1776                logger.debug("Error re-indexing the field " + this.getID()
1777                + " after restore version operation.",t);
1778            }
1779        }
1780        return result;
1781    }
1782
1783    /**
1784     * Returns a JahiaField object corresponding to the current content field
1785     * and the specified entry load request.
1786     *
1787     * @param entryLoadRequest the entry load request for which to creating the
1788     * JahiaField instance
1789     *
1790     * @return a JahiaField of the correct instance corresponding to the
1791     * content field and the EntryLoadRequest.
1792     *
1793     * @throws JahiaException if there was an error while creating the
1794     * JahiaField instance.
1795     */

1796    public JahiaField getJahiaField (EntryLoadRequest entryLoadRequest)
1797            throws JahiaException {
1798        return ServicesRegistry.getInstance ().getJahiaFieldService ().
1799                contentFieldToJahiaField (this, entryLoadRequest);
1800    }
1801
1802    /**
1803     * update ContainersChangeEventListener listener
1804     *
1805     * @param operation @see ContainersChangeEventListener
1806     */

1807    private void notifyFieldUpdate(){
1808
1809        FieldsChangeEventListener listener = (
1810                FieldsChangeEventListener)JahiaListenersRegistry.
1811                getInstance ()
1812                .getListener (FieldsChangeEventListener.class.getName ());
1813        if ( listener != null ){
1814            listener.notifyChange(this);
1815        }
1816    }
1817
1818    /**
1819     * Returns the biggest versionID of the active entry, 0 if doesn't exist
1820     *
1821     * @return
1822     *
1823     * @throws JahiaException
1824     */

1825    public int getActiveVersionID ()
1826            throws JahiaException {
1827        int versionID = 0;
1828        Iterator iterator = this.getActiveAndStagingEntryStates().iterator ();
1829        ContentObjectEntryState entryState = null;
1830        while (iterator.hasNext ()) {
1831            entryState = (ContentObjectEntryState) iterator.next ();
1832            if ( entryState.getWorkflowState() == EntryLoadRequest.ACTIVE_WORKFLOW_STATE
1833                 && entryState.getVersionID() > versionID ){
1834                     versionID = entryState.getVersionID();
1835            }
1836        }
1837        return versionID;
1838    }
1839
1840}
1841
Popular Tags