001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.workflow;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.io.IOUtils;
031import org.apache.commons.lang.StringUtils;
032
033import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
034import org.ametys.cms.contenttype.ContentTypesHelper;
035import org.ametys.cms.contenttype.MetadataDefinition;
036import org.ametys.cms.contenttype.MetadataManager;
037import org.ametys.cms.contenttype.RepeaterDefinition;
038import org.ametys.cms.repository.Content;
039import org.ametys.cms.repository.ModifiableWorkflowAwareContent;
040import org.ametys.cms.repository.WorkflowAwareContent;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.util.JSONUtils;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.lock.LockHelper;
045import org.ametys.plugins.repository.lock.LockableAmetysObject;
046import org.ametys.plugins.repository.metadata.BinaryMetadata;
047import org.ametys.plugins.repository.metadata.CompositeMetadata;
048import org.ametys.plugins.repository.metadata.File;
049import org.ametys.plugins.repository.metadata.Folder;
050import org.ametys.plugins.repository.metadata.MultilingualString;
051import org.ametys.plugins.repository.metadata.MultilingualStringHelper;
052import org.ametys.plugins.repository.metadata.Resource;
053import org.ametys.plugins.repository.metadata.RichText;
054import org.ametys.plugins.repository.metadata.UnknownMetadataException;
055import org.ametys.plugins.repository.version.VersionableAmetysObject;
056import org.ametys.runtime.parameter.ParameterHelper;
057
058import com.opensymphony.module.propertyset.PropertySet;
059import com.opensymphony.workflow.WorkflowException;
060
061/**
062 * OSWorkflow function to restore an old revision of a content.
063 * Builds a Map with the old content's metadata values, and passes it to the
064 * {@link EditContentFunction}, which does the real job.
065 */
066public class RestoreRevisionFunction extends AbstractContentFunction
067{
068    
069    /** The ametys object resolver. */
070    protected AmetysObjectResolver _resolver;
071    
072    /** The content type extension point. */
073    protected ContentTypeExtensionPoint _cTypeEP;
074    
075    /** The content type helper. */
076    protected ContentTypesHelper _cTypeHelper;
077    
078    /** The JSON conversion utilities. */
079    protected JSONUtils _jsonUtils;
080    
081    @Override
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        super.service(manager);
085        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
086        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
087        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
088        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
089    }
090    
091    @Override
092    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
093    {
094        WorkflowAwareContent content = getContent(transientVars);
095        UserIdentity user = getUser(transientVars);
096        
097        if (!(content instanceof ModifiableWorkflowAwareContent))
098        {
099            throw new IllegalArgumentException("The provided content " + content.getId() + " is not a ModifiableWorkflowAwareContent.");
100        }
101        
102        ModifiableWorkflowAwareContent modifiableContent = (ModifiableWorkflowAwareContent) content;
103        
104        if (content instanceof LockableAmetysObject)
105        {
106            LockableAmetysObject lockableContent = (LockableAmetysObject) content;
107            if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, user))
108            {
109                throw new WorkflowException ("The user '" + user + "' try to restore the content '" + content.getId() + "', but this content is locked by the user '" + user + "'");
110            }
111            else if (lockableContent.isLocked())
112            {
113                lockableContent.unlock();
114            }
115        }
116        
117        String contentVersion = (String) getContextParameters(transientVars).get("contentVersion");
118        
119        Content oldContent = _resolver.resolveById(content.getId());
120        if (oldContent instanceof VersionableAmetysObject)
121        {
122            ((VersionableAmetysObject) oldContent).switchToRevision(contentVersion);
123        }
124        
125        Map<String, Object> results = getResultsMap(transientVars);
126        Map<String, Object> brokenReferences = new HashMap<>();
127        results.put("brokenReferences", brokenReferences);
128        
129        Map<String, Object> parameters = getContextParameters(transientVars);
130        Map<String, Object> newValues = new HashMap<>();
131        parameters.put(EditContentFunction.FORM_RAW_VALUES, newValues);
132        
133        parameters.put(EditContentFunction.QUIT, Boolean.TRUE);
134        
135        // Recursively get the old content's metadata values, and fill the "new values" map with them.
136        // Additionally, detect broken references.
137        processMetadatas(content, oldContent, newValues, brokenReferences);
138        
139        modifiableContent.setLastContributor(user);
140        modifiableContent.setLastModified(new Date());
141        // Remove the proposal date.
142        modifiableContent.setProposalDate(null);
143        
144        // Commit changes
145        modifiableContent.saveChanges();
146    }
147    
148    /**
149     * Process the old content metadatas.
150     * @param content the current content (the one being modified).
151     * @param oldContent the old content.
152     * @param newValues the Map to fill with the old content's values.
153     * @param brokenReferences the Map of broken references (which could not be restored).
154     * @throws WorkflowException if an error occurs.
155     */
156    protected void processMetadatas(Content content, Content oldContent, Map<String, Object> newValues, Map<String, Object> brokenReferences) throws WorkflowException
157    {
158        CompositeMetadata metaHolder = oldContent.getMetadataHolder();
159        
160        // Browse the current content's model.
161        Set<String> metadataNames = _cTypeHelper.getMetadataNames(content);
162        for (String metaName : metadataNames)
163        {
164            MetadataDefinition metaDef = _cTypeHelper.getMetadataDefinition(metaName, content);
165            
166            processMetadata(metaHolder, metaName, metaDef, metaName, newValues, brokenReferences);
167        }
168    }
169    
170    /**
171     * Process the old content metadatas.
172     * @param metaHolder the source metadata holder.
173     * @param metaDef the metadata definition.
174     * @param metaPath the metadata path.
175     * @param newValues the Map to fill with the old content's values.
176     * @param brokenReferences the Map of broken references (which could not be restored).
177     * @throws WorkflowException if an error occurs.
178     */
179    protected void processMetadatas(CompositeMetadata metaHolder, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues, Map<String, Object> brokenReferences) throws WorkflowException
180    {
181        Set<String> subMetadataNames = metaDef.getMetadataNames();
182        for (String subMetaName : subMetadataNames)
183        {
184            MetadataDefinition subMetaDef = metaDef.getMetadataDefinition(subMetaName);
185            String subMetaPath = metaPath + "." + subMetaName;
186            
187            processMetadata(metaHolder, subMetaName, subMetaDef, subMetaPath, newValues, brokenReferences);
188        }
189    }
190    
191    /**
192     * Process a metadata from the old content.
193     * @param metaHolder the source metadata holder.
194     * @param metaName the metadata name.
195     * @param metaDef the metadata definition.
196     * @param metaPath the metadata path.
197     * @param newValues the Map to fill with the old content's values.
198     * @param brokenReferences the Map of broken references (which could not be restored).
199     * @throws WorkflowException if an error occurs.
200     */
201    protected void processMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues, Map<String, Object> brokenReferences) throws WorkflowException
202    {
203        if (metaDef != null)
204        {
205            switch (metaDef.getType())
206            {
207                case STRING:
208                case LONG:
209                case DOUBLE:
210                case BOOLEAN:
211                    processStringMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
212                    break;
213                case MULTILINGUAL_STRING:
214                    processMultilingualStringMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
215                    break;
216                case USER:
217                    processUserMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
218                    break;
219                case DATE:
220                    processDateMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
221                    break;
222                case DATETIME:
223                    processDatetimeMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
224                    break;
225                case GEOCODE:
226                    processGeocodeMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
227                    break;
228                case RICH_TEXT:
229                    processRichtextMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
230                    break;
231                case BINARY:
232                    processBinaryMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
233                    break;
234                case FILE:
235                    processFileMetadata(metaHolder, metaName, metaDef, metaPath, newValues);
236                    break;
237                case REFERENCE:
238                    processReferenceMetadata(metaHolder, metaName, metaDef, metaPath, newValues, brokenReferences);
239                    break;
240                case CONTENT:
241                    processContentMetadata(metaHolder, metaName, metaDef, metaPath, newValues, brokenReferences);
242                    break;
243                case SUB_CONTENT:
244                    break;
245                case COMPOSITE:
246                    processCompositeMetadata(metaHolder, metaName, metaDef, metaPath, newValues, brokenReferences);
247                    break;
248                default:
249                    break;
250            }
251        }
252    }
253    
254    /**
255     * Get the value from a String old content metadata and put it in the new values map.
256     * @param metaHolder the source metadata holder.
257     * @param metaName the metadata name.
258     * @param metaDef the metadata definition.
259     * @param metaPath the metadata path.
260     * @param newValues the Map to fill with the old content's values.
261     */
262    protected void processStringMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
263    {
264        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
265        
266        if (metaDef.isMultiple())
267        {
268            String[] valuesArr = metaHolder.getStringArray(metaName, new String[0]);
269            newValues.put(valueKey, Arrays.asList(valuesArr));
270        }
271        else
272        {
273            String value = metaHolder.getString(metaName, null);
274            newValues.put(valueKey, value);
275        }
276    }
277    
278    /**
279     * Get the value from a Date old content metadata and put it in the new values map.
280     * @param metaHolder the source metadata holder.
281     * @param metaName the metadata name.
282     * @param metaDef the metadata definition.
283     * @param metaPath the metadata path.
284     * @param newValues the Map to fill with the old content's values.
285     */
286    protected void processDateMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
287    {
288        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
289        
290        if (metaDef.isMultiple())
291        {
292            Date[] valuesArr = metaHolder.getDateArray(metaName, new Date[0]);
293            List<String> values = new ArrayList<>();
294            for (Date value : valuesArr)
295            {
296                values.add(ParameterHelper.valueToString(value));
297            }
298            
299            newValues.put(valueKey, values);
300        }
301        else
302        {
303            Date value = metaHolder.getDate(metaName, null);
304            newValues.put(valueKey, StringUtils.trimToEmpty(ParameterHelper.valueToString(value)));
305        }
306    }
307    
308    /**
309     * Get the value from a DateTime old content metadata and put it in the new values map.
310     * @param metaHolder the source metadata holder.
311     * @param metaName the metadata name.
312     * @param metaDef the metadata definition.
313     * @param metaPath the metadata path.
314     * @param newValues the Map to fill with the old content's values.
315     */
316    protected void processDatetimeMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
317    {
318        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
319        
320        if (metaDef.isMultiple())
321        {
322            Date[] valuesArr = metaHolder.getDateArray(metaName, new Date[0]);
323            List<String> values = new ArrayList<>();
324            for (Date value : valuesArr)
325            {
326                values.add(ParameterHelper.valueToString(value));
327            }
328            
329            newValues.put(valueKey, values);
330        }
331        else
332        {
333            Date value = metaHolder.getDate(metaName, null);
334            newValues.put(valueKey, StringUtils.trimToEmpty(ParameterHelper.valueToString(value)));
335        }
336    }
337    
338    /**
339     * Get the value from a geocode old content metadata and put it in the new values map.
340     * @param metaHolder the source metadata holder.
341     * @param metaName the metadata name.
342     * @param metaDef the metadata definition.
343     * @param metaPath the metadata path.
344     * @param newValues the Map to fill with the old content's values.
345     */
346    protected void processGeocodeMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
347    {
348        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
349        
350        Map<String, Object> values = new HashMap<>();
351        
352        try
353        {
354            CompositeMetadata meta = metaHolder.getCompositeMetadata(metaName);
355            
356            values.put("longitude", meta.getDouble("longitude"));
357            values.put("latitude", meta.getDouble("latitude"));
358            
359            String jsonValues = _jsonUtils.convertObjectToJson(values);
360            
361            newValues.put(valueKey, jsonValues);
362        }
363        catch (UnknownMetadataException e)
364        {
365            // The composite metadata doesn't exist: store null
366            newValues.put(valueKey, null);
367        }
368    }
369    
370    /**
371     * Get the value from a user old content metadata and put it in the new values map.
372     * @param metaHolder the source metadata holder.
373     * @param metaName the metadata name.
374     * @param metaDef the metadata definition.
375     * @param metaPath the metadata path.
376     * @param newValues the Map to fill with the old content's values.
377     */
378    protected void processUserMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
379    {
380        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
381        
382        Map<String, Object> values = new HashMap<>();
383        
384        try
385        {
386            CompositeMetadata meta = metaHolder.getCompositeMetadata(metaName);
387            
388            values.put("login", meta.getString("login"));
389            values.put("populationId", meta.getString("populationId"));
390            
391            String jsonValues = _jsonUtils.convertObjectToJson(values);
392            
393            newValues.put(valueKey, jsonValues);
394        }
395        catch (UnknownMetadataException e)
396        {
397            // The composite metadata doesn't exist: store null
398            newValues.put(valueKey, null);
399        }
400    }
401    
402    /**
403     * Get the value from a {@link MultilingualString} old content metadata and put it in the new values map.
404     * @param metaHolder the source metadata holder.
405     * @param metaName the metadata name.
406     * @param metaDef the metadata definition.
407     * @param metaPath the metadata path.
408     * @param newValues the Map to fill with the old content's values.
409     */
410    protected void processMultilingualStringMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
411    {
412        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
413        
414        try
415        {
416            MultilingualString meta = metaHolder.getMultilingualString(metaName);
417            
418            Map<String, Object> json = MultilingualStringHelper.toJson(meta);
419            
420            String jsonValues = _jsonUtils.convertObjectToJson(json);
421            
422            newValues.put(valueKey, jsonValues);
423        }
424        catch (UnknownMetadataException e)
425        {
426            // The composite metadata doesn't exist: store null
427            newValues.put(valueKey, null);
428        }
429    }
430    
431    /**
432     * Get the value from a RichText old content metadata and put it in the new values map.
433     * @param metaHolder the source metadata holder.
434     * @param metaName the metadata name.
435     * @param metaDef the metadata definition.
436     * @param metaPath the metadata path.
437     * @param newValues the Map to fill with the old content's values.
438     * @throws WorkflowException if an error occurs.
439     */
440    protected void processRichtextMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues) throws WorkflowException
441    {
442        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
443        String formatKey = EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metaPath + ".format";
444        String addDataKey = EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metaPath + ".additionalData";
445        
446        try
447        {
448            RichText richText = metaHolder.getRichText(metaName);
449            String docbookContent = IOUtils.toString(richText.getInputStream(), "UTF-8");
450            
451            Folder dataFolder = richText.getAdditionalDataFolder();
452            Map<String, Resource> datas = new LinkedHashMap<>();
453            for (File file : dataFolder.getFiles())
454            {
455                datas.put(file.getName(), file.getResource());
456            }
457            
458            newValues.put(valueKey, docbookContent);
459            newValues.put(formatKey, "docbook");
460            newValues.put(addDataKey, datas);
461        }
462        catch (UnknownMetadataException e)
463        {
464            // The rich-text metadata doesn't exist: store null
465            newValues.put(valueKey, null);
466        }
467        catch (IOException e)
468        {
469            // Error reading the rich-text content.
470            throw new WorkflowException("Error reading the rich-text content for metadata " + metaPath, e);
471        }
472    }
473    
474    /**
475     * Get the value from a binary old content metadata and put it in the new values map.
476     * @param metaHolder the source metadata holder.
477     * @param metaName the metadata name.
478     * @param metaDef the metadata definition.
479     * @param metaPath the metadata path.
480     * @param newValues the Map to fill with the old content's values.
481     */
482    protected void processBinaryMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
483    {
484        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
485        String rawValueKey = EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metaPath + ".rawValue";
486        
487        if (metaHolder.hasMetadata(metaName))
488        {
489            BinaryMetadata value = metaHolder.getBinaryMetadata(metaName);
490            
491            newValues.put(valueKey, EditContentFunction.UNTOUCHED_BINARY);
492            newValues.put(rawValueKey, value);
493        }
494        else
495        {
496            newValues.put(valueKey, null);
497        }
498    }
499    
500    /**
501     * Get the value from a File old content metadata and put it in the new values map.
502     * @param metaHolder the source metadata holder.
503     * @param metaName the metadata name.
504     * @param metaDef the metadata definition.
505     * @param metaPath the metadata path.
506     * @param newValues the Map to fill with the old content's values.
507     */
508    protected void processFileMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues)
509    {
510        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
511        
512        if (metaHolder.hasMetadata(metaName))
513        {
514            Map<String, Object> values = new HashMap<>();
515            
516            if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(metaHolder.getType(metaName)))
517            {
518                BinaryMetadata value = metaHolder.getBinaryMetadata(metaName);
519                values.put("type", "metadata");
520                values.put("id", "modified");
521                
522                String rawValueKey = EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + metaPath + ".rawValue";
523                newValues.put(rawValueKey, value);
524            }
525            else
526            {
527                // If it's not a binary, it's an explorer resource ID: process it as a simple string.
528                String value = metaHolder.getString(metaName, null);
529                values.put("type", "explorer");
530                values.put("id", value);
531            }
532            
533            String jsonValues = _jsonUtils.convertObjectToJson(values);
534            newValues.put(valueKey, jsonValues);
535        }
536        else
537        {
538            newValues.put(valueKey, null);
539        }
540    }
541    
542    /**
543     * Get the value from a Reference old content metadata and put it in the new values map.
544     * @param metaHolder the source metadata holder.
545     * @param metaName the metadata name.
546     * @param metaDef the metadata definition.
547     * @param metaPath the metadata path.
548     * @param newValues the Map to fill with the old content's values.
549     * @param brokenReferences the Map of broken references (which could not be restored). 
550     */
551    protected void processReferenceMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues, Map<String, Object> brokenReferences)
552    {
553        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
554        
555        Map<String, Object> values = new HashMap<>();
556        
557        try
558        {
559            CompositeMetadata meta = metaHolder.getCompositeMetadata(metaName);
560            
561            values.put("value", meta.getString("value"));
562            values.put("type", meta.getString("type"));
563            
564            String jsonValues = _jsonUtils.convertObjectToJson(values);
565            
566            newValues.put(valueKey, jsonValues);
567        }
568        catch (UnknownMetadataException e)
569        {
570            // The composite metadata doesn't exist: store null
571            newValues.put(valueKey, null);
572        }
573    }
574    
575    /**
576     * Get the value from a Content old content metadata and put it in the new values map.
577     * @param metaHolder the source metadata holder.
578     * @param metaName the metadata name.
579     * @param metaDef the metadata definition.
580     * @param metaPath the metadata path.
581     * @param newValues the Map to fill with the old content's values.
582     * @param brokenReferences the Map of broken references (which could not be restored).
583     */
584    protected void processContentMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues, Map<String, Object> brokenReferences)
585    {
586        String valueKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath;
587        
588        if (metaDef.isMultiple())
589        {
590            String[] contentIds = metaHolder.getStringArray(metaName, new String[0]);
591            List<String> validIds = new ArrayList<>(contentIds.length);
592            
593            for (String value : contentIds)
594            {
595                if (_resolver.hasAmetysObjectForId(value))
596                {
597                    validIds.add(value);
598                }
599                else if (!brokenReferences.containsKey(metaName))
600                {
601                    brokenReferences.put(metaName, metaDef.getLabel());
602                }
603            }
604            
605            // Set the value (even if it's an empty array, to delete potentially existing values).
606            newValues.put(valueKey, validIds);
607        }
608        else
609        {
610            String value = metaHolder.getString(metaName, null);
611            if (value != null)
612            {
613                if (_resolver.hasAmetysObjectForId(value))
614                {
615                    newValues.put(valueKey, value);
616                }
617                else
618                {
619                    brokenReferences.put(metaName, metaDef.getLabel());
620                }
621            }
622            else
623            {
624                // Set the value even if it's null, to delete a potentially existing value.
625                newValues.put(valueKey, null);
626            }
627        }
628    }
629    
630    /**
631     * Get the value from a Composite old content metadata and put it in the new values map.
632     * @param metaHolder the source metadata holder.
633     * @param metaName the metadata name.
634     * @param metaDef the metadata definition.
635     * @param metaPath the metadata path.
636     * @param newValues the Map to fill with the old content's values.
637     * @param brokenReferences the Map of broken references (which could not be restored).
638     * @throws WorkflowException if an error occurs. 
639     */
640    protected void processCompositeMetadata(CompositeMetadata metaHolder, String metaName, MetadataDefinition metaDef, String metaPath, Map<String, Object> newValues, Map<String, Object> brokenReferences) throws WorkflowException
641    {
642        if (metaDef instanceof RepeaterDefinition)
643        {
644            if (metaHolder.hasMetadata(metaName))
645            {
646                CompositeMetadata repeaterMetaHolder = metaHolder.getCompositeMetadata(metaName);
647                
648                String[] entryNames = repeaterMetaHolder.getMetadataNames();
649                Arrays.sort(entryNames, MetadataManager.REPEATER_ENTRY_COMPARATOR);
650                
651                newValues.put("_" + EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath + ".size", Integer.toString(entryNames.length));
652                
653                if (entryNames.length < 1)
654                {
655                    newValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath, null);
656                }
657                
658                for (String entryName : entryNames)
659                {
660                    CompositeMetadata entryMetaHolder = repeaterMetaHolder.getCompositeMetadata(entryName);
661                    String entryPath = metaPath + "." + entryName;
662                    
663                    processMetadatas(entryMetaHolder, metaDef, entryPath, newValues, brokenReferences);
664                }
665            }
666            else
667            {
668                newValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath, null);
669                newValues.put("_" + EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath + ".size", "0");
670            }
671        }
672        else
673        {
674            if (metaHolder.hasMetadata(metaName))
675            {
676                CompositeMetadata compositeMetaHolder = metaHolder.getCompositeMetadata(metaName);
677                processMetadatas(compositeMetaHolder, metaDef, metaPath, newValues, brokenReferences);
678            }
679            else
680            {
681                newValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metaPath, null);
682            }
683        }
684    }
685    
686}