001/*
002 *  Copyright 2011 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.web.repository.page;
017
018import java.io.InputStream;
019import java.io.OutputStream;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025import java.util.Set;
026
027import javax.jcr.Node;
028import javax.jcr.RepositoryException;
029import javax.xml.transform.OutputKeys;
030import javax.xml.transform.TransformerFactory;
031import javax.xml.transform.sax.SAXTransformerFactory;
032import javax.xml.transform.sax.TransformerHandler;
033import javax.xml.transform.stream.StreamResult;
034
035import org.apache.avalon.framework.component.Component;
036import org.apache.avalon.framework.logger.AbstractLogEnabled;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.excalibur.xml.sax.SAXParser;
041import org.apache.xml.serializer.OutputPropertiesFactory;
042import org.xml.sax.ContentHandler;
043import org.xml.sax.InputSource;
044
045import org.ametys.cms.content.references.OutgoingReferences;
046import org.ametys.cms.content.references.OutgoingReferencesExtractor;
047import org.ametys.cms.contenttype.ContentType;
048import org.ametys.cms.contenttype.RichTextUpdater;
049import org.ametys.cms.data.RichText;
050import org.ametys.cms.repository.Content;
051import org.ametys.cms.repository.ModifiableContent;
052import org.ametys.cms.repository.WorkflowAwareContent;
053import org.ametys.cms.repository.WorkflowAwareContentHelper;
054import org.ametys.plugins.repository.AmetysObject;
055import org.ametys.plugins.repository.AmetysObjectIterable;
056import org.ametys.plugins.repository.AmetysObjectResolver;
057import org.ametys.plugins.repository.AmetysRepositoryException;
058import org.ametys.plugins.repository.ModifiableAmetysObject;
059import org.ametys.plugins.repository.RepositoryConstants;
060import org.ametys.plugins.repository.TraversableAmetysObject;
061import org.ametys.plugins.repository.UnknownAmetysObjectException;
062import org.ametys.plugins.repository.data.UnknownDataException;
063import org.ametys.plugins.repository.data.holder.DataHolder;
064import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
065import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
066import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
067import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
068import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
069import org.ametys.plugins.repository.data.holder.group.Repeater;
070import org.ametys.plugins.repository.data.holder.group.RepeaterEntry;
071import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper;
072import org.ametys.plugins.repository.data.type.ModelItemTypeConstants;
073import org.ametys.plugins.repository.version.VersionableAmetysObject;
074import org.ametys.plugins.workflow.support.WorkflowProvider;
075import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
076import org.ametys.runtime.model.ModelItem;
077import org.ametys.runtime.model.exception.NotUniqueTypeException;
078import org.ametys.runtime.model.exception.UndefinedItemPathException;
079import org.ametys.runtime.model.exception.UnknownTypeException;
080import org.ametys.web.repository.ModifiableSiteAwareAmetysObject;
081import org.ametys.web.repository.content.WebContent;
082import org.ametys.web.repository.page.ZoneItem.ZoneType;
083import org.ametys.web.repository.site.Site;
084import org.ametys.web.repository.sitemap.Sitemap;
085import org.ametys.web.site.CopyUpdaterExtensionPoint;
086
087import com.opensymphony.workflow.spi.Step;
088
089/**
090 * Component for copying site or pages
091 *
092 */
093public class CopySiteComponent extends AbstractLogEnabled implements Component, Serviceable
094{
095    /** Avalon Role */
096    public static final String ROLE = CopySiteComponent.class.getName();
097    
098    /** The service manager. */
099    protected ServiceManager _manager;
100    
101    private AmetysObjectResolver _resolver;
102    private WorkflowProvider _workflowProvider;
103    
104    private CopyUpdaterExtensionPoint _updaterEP;
105    private OutgoingReferencesExtractor _outgoingReferencesExtractor;
106    
107    @Override
108    public void service(ServiceManager serviceManager) throws ServiceException
109    {
110        _manager = serviceManager;
111        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
112        _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE);
113        _updaterEP = (CopyUpdaterExtensionPoint) serviceManager.lookup(CopyUpdaterExtensionPoint.ROLE);
114        _outgoingReferencesExtractor = (OutgoingReferencesExtractor) serviceManager.lookup(OutgoingReferencesExtractor.ROLE);
115    }
116
117    /**
118     * This methods must be used after calling <code>copyTo</code> on a Page.
119     * Its updates references to ametys objects for metadata of new created pages and contents
120     * @param originalPage the original page
121     * @param createdPage the created page after a copy
122     * @throws AmetysRepositoryException if an error occurs
123     */
124    public void updateReferencesAfterCopy (Page originalPage, Page createdPage) throws AmetysRepositoryException
125    {
126        // Update references to ametys object on metadata
127        _updateReferencesToAmetysObjects (createdPage, originalPage, createdPage);
128        
129        for (Zone zone : createdPage.getZones())
130        {
131            _updateReferencesToAmetysObjects (zone, originalPage, createdPage);
132            
133            for (ZoneItem zoneItem : zone.getZoneItems())
134            {
135                _updateReferencesToAmetysObjects (zoneItem, originalPage, createdPage);
136                
137                if (zoneItem.getType().equals(ZoneType.SERVICE))
138                {
139                    _updateReferencesToAmetysObjects (zoneItem, originalPage, createdPage);
140                }
141                else
142                {
143                    Content content = zoneItem.getContent();
144                    _updateReferencesToAmetysObjects(content, originalPage, createdPage);
145                }
146            }
147        }
148        
149        // Browse child pages
150        for (Page childPage : createdPage.getChildrenPages())
151        {
152            updateReferencesAfterCopy ((Page) originalPage.getChild(childPage.getName()), childPage);
153        }
154    }
155    
156    /**
157     * This method must be used after calling <code>copyTo</code> on a Site.
158     * Its updates contents and pages after a site copy
159     * @param originalSite the original site
160     * @param createdSite the created site after copy
161     * @throws AmetysRepositoryException if an error occurs
162     */
163    public void updateSiteAfterCopy (Site originalSite, Site createdSite) throws AmetysRepositoryException
164    {
165        updateContentsAfterCopy (originalSite, createdSite);
166        updatePagesAfterCopy (originalSite, createdSite);
167        
168        Set<String> ids = _updaterEP.getExtensionsIds();
169        for (String id : ids)
170        {
171            _updaterEP.getExtension(id).updateSite(originalSite, createdSite);
172        }
173    }
174    
175    /**
176     * This method re-initializes workflow, updates the site name for web content and updates references to ametys objects on metadata after a site copy
177     * @param initialSite the original site
178     * @param createdSite the created site after copy
179     * @throws AmetysRepositoryException if an error occurs 
180     */
181    public void updateContentsAfterCopy (Site initialSite, Site createdSite) throws AmetysRepositoryException
182    {
183        AmetysObjectIterable<Content> contents = createdSite.getContents();
184        for (Content content : contents)
185        {
186            String relPath = content.getPath().substring(createdSite.getPath().length() + 1);
187            WebContent initialContent = initialSite.getChild(relPath);
188            
189            try
190            {
191                // Re-init workflow
192                if (content instanceof WorkflowAwareContent)
193                {
194                    _reinitWorkflow ((WorkflowAwareContent) content);
195                }
196                
197                // Update site name
198                if (content instanceof ModifiableSiteAwareAmetysObject)
199                {
200                    ((ModifiableSiteAwareAmetysObject) content).setSiteName(createdSite.getName());
201                }
202                
203                // Update references to ametys object on attributes
204                _updateReferencesToAmetysObjects(content, initialSite, createdSite);
205                
206                // Update links in RichText
207                updateLinksInRichText (initialSite, createdSite, initialContent, content);
208                
209                // Updaters
210                Set<String> ids = _updaterEP.getExtensionsIds();
211                for (String id : ids)
212                {
213                    _updaterEP.getExtension(id).updateContent(initialSite, createdSite, initialContent, content);
214                }
215                
216                // save
217                if (content instanceof ModifiableAmetysObject)
218                {
219                    ((ModifiableAmetysObject) content).saveChanges();
220                }
221                
222                // Creates the first version
223                if (content instanceof VersionableAmetysObject)
224                {
225                    ((VersionableAmetysObject) content).checkpoint();
226                }
227            }
228            catch (Exception e)
229            {
230                // Do not make the copy fail.
231                getLogger().warn("[Site copy] An error occured while updating content '" + content.getId() + " after copy from initial content '" + initialContent.getId() + "'", e);
232            }
233        }
234        
235        if (createdSite.needsSave())
236        {
237            createdSite.saveChanges();
238        }
239    }
240
241    /**
242     * Updates references all references in a content to another one.
243     * @param initialContent the initial content.
244     * @param destContent the destination content.
245     */
246    public void updateSharedContent(WebContent initialContent, WebContent destContent)
247    {
248        updateSharedContent(initialContent, destContent, true);
249    }
250    
251    /**
252     * Updates references all references in a content to another one.
253     * @param initialContent the initial content.
254     * @param destContent the destination content.
255     * @param reinitWorkflow set to 'true' to reinitialize the workflow
256     */
257    public void updateSharedContent(WebContent initialContent, WebContent destContent, boolean reinitWorkflow)
258    {
259        Site initialSite = initialContent.getSite();
260        Site createdSite = destContent.getSite();
261        
262        // Re-init workflow
263        if (reinitWorkflow && destContent instanceof WorkflowAwareContent)
264        {
265            _reinitWorkflow((WorkflowAwareContent) destContent);
266        }
267        
268        // Update references to ametys object on attributes
269        _updateReferencesToAmetysObjects(destContent, initialContent, destContent);
270        
271        // Update links in RichText
272        updateLinksInRichText(initialContent, destContent, initialContent, destContent);
273        
274        // Updaters
275        Set<String> ids = _updaterEP.getExtensionsIds();
276        for (String id : ids)
277        {
278            _updaterEP.getExtension(id).updateContent(initialSite, createdSite, initialContent, destContent);
279        }
280        
281        // save
282        if (destContent instanceof ModifiableAmetysObject)
283        {
284            ((ModifiableAmetysObject) destContent).saveChanges();
285        }
286        
287        // Creates the first version
288        if (destContent instanceof VersionableAmetysObject)
289        {
290            ((VersionableAmetysObject) destContent).checkpoint();
291        }
292    }
293    
294    /**
295     * This method analyzes content rich texts and update links if necessary
296     * @param initialAO The initial object copied
297     * @param createdAO The target object 
298     * @param initialContent The initial content
299     * @param createdContent The created content after copy to update
300     * @throws AmetysRepositoryException if an error occurs
301     */
302    public void updateLinksInRichText (TraversableAmetysObject initialAO, TraversableAmetysObject createdAO, Content initialContent, Content createdContent) throws AmetysRepositoryException
303    {
304        SAXParser saxParser = null;
305        try
306        {
307            if (createdContent instanceof ModifiableContent)
308            {
309                saxParser = (SAXParser) _manager.lookup(SAXParser.ROLE);
310                
311                Map<String, Object> params = new HashMap<>();
312                params.put("initialContent", initialContent);
313                params.put("createdContent", createdContent);
314                params.put("initialAO", initialAO);
315                params.put("createdAO", createdAO);
316                
317                Map<String, Object> richTexts = DataHolderHelper.findItemsByType(createdContent, org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID);
318                for (Map.Entry<String, Object> entry : richTexts.entrySet())
319                {
320                    Object value = entry.getValue();
321                    if (value != null)
322                    {
323                        String attributePath = entry.getKey();
324                        ModelItem attributeDefinition = createdContent.getDefinition(attributePath);
325                        ContentType contentType = (ContentType) attributeDefinition.getModel();
326                        RichTextUpdater richTextUpdater = contentType.getRichTextUpdater();
327                        
328                        if (value instanceof RichText)
329                        {
330                            _updateRichText((RichText) value, richTextUpdater, params, saxParser);
331                        }
332                        else if (value instanceof RichText[])
333                        {
334                            for (RichText richText : (RichText[]) value)
335                            {
336                                _updateRichText(richText, richTextUpdater, params, saxParser);
337                            }
338                        }
339                        
340                        ((ModifiableContent) createdContent).setValue(attributePath, value);
341                    }
342                }
343            
344                // Outgoing references
345                Map<String, OutgoingReferences> outgoingReferencesByPath = _outgoingReferencesExtractor.getOutgoingReferences(createdContent);
346                ((ModifiableContent) createdContent).setOutgoingReferences(outgoingReferencesByPath);
347            }
348        }
349        catch (Exception e)
350        {
351            // Do not failed the copy
352            getLogger().warn("An error occured while updating links in RichText for content '" + createdContent.getId() + " after copy from initial content '" + initialContent.getId() + "'", e);
353        }
354        finally
355        {
356            _manager.release(saxParser);
357        }
358    }
359
360    private void _updateRichText(RichText richText, RichTextUpdater richTextUpdater, Map<String, Object> params, SAXParser saxParser) throws Exception
361    {
362        try (InputStream is = richText.getInputStream(); OutputStream os = richText.getOutputStream())
363        {
364            // create a transformer for saving sax into a file
365            TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler();
366
367            // create the result where to write
368            StreamResult result = new StreamResult(os);
369            th.setResult(result);
370
371            // create the format of result
372            Properties format = new Properties();
373            format.put(OutputKeys.METHOD, "xml");
374            format.put(OutputKeys.INDENT, "yes");
375            format.put(OutputKeys.ENCODING, "UTF-8");
376            format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2");
377            th.getTransformer().setOutputProperties(format);
378
379            ContentHandler richTextHandler = richTextUpdater.getContentHandler(th, th, params);
380            saxParser.parse(new InputSource(is), richTextHandler);
381        }
382    }
383    
384    /**
385     * This method updates the site name of pages and updates references to ametys objects on page's metadata after a site copy
386     * @param originalSite the original site
387     * @param createdSite the created site after copy
388     * @throws AmetysRepositoryException if an error occurs 
389     */
390    public void updatePagesAfterCopy (Site originalSite, Site createdSite) throws AmetysRepositoryException
391    {
392        AmetysObjectIterable<Sitemap> sitemaps = createdSite.getSitemaps();
393        
394        for (Sitemap sitemap : sitemaps)
395        {
396            for (Page page : sitemap.getChildrenPages())
397            {
398                _updatePageAfterCopy (originalSite, createdSite, page);
399            }
400        }
401    }
402    
403    private void _updatePageAfterCopy (Site originalSite, Site createdSite, Page page) throws AmetysRepositoryException
404    {
405        try
406        {
407            // Update site name
408            if (page instanceof ModifiablePage)
409            {
410                ((ModifiablePage) page).setSiteName(createdSite.getName());
411            }
412            
413            // Update references to ametys object on metadata
414            _updateReferencesToAmetysObjects (page, originalSite, createdSite);
415            
416            for (Zone zone : page.getZones())
417            {
418                _updateReferencesToAmetysObjects (zone, originalSite, createdSite);
419                
420                for (ZoneItem zoneItem : zone.getZoneItems())
421                {
422                    try
423                    {
424                        _updateZoneItemAfterCopy(originalSite, createdSite, zoneItem);
425                    }
426                    catch (Exception e)
427                    {
428                        getLogger().warn("An error occured while updating zone item '" + zoneItem.getId() + "' (" + zoneItem.getPath() + ") after copy", e);
429                    }
430                }
431            }
432        }
433        catch (AmetysRepositoryException e)
434        {
435            // Do not failed the copy
436            getLogger().warn("An error occured while updating page '" + page.getId() + "' (" + page.getPathInSitemap() + ") after copy", e);
437        }
438        
439        // Browse child pages
440        for (Page childPage : page.getChildrenPages())
441        {
442            _updatePageAfterCopy (originalSite, createdSite, childPage);
443        }
444        
445        // Updaters
446        Set<String> ids = _updaterEP.getExtensionsIds();
447        for (String id : ids)
448        {
449            _updaterEP.getExtension(id).updatePage(originalSite, createdSite, page);
450        }
451    }
452
453    private void _updateZoneItemAfterCopy(Site originalSite, Site createdSite, ZoneItem zoneItem)
454    {
455        _updateReferencesToAmetysObjects (zoneItem, originalSite, createdSite);
456        
457        if (zoneItem.getType().equals(ZoneType.SERVICE))
458        {
459            _updateReferencesToAmetysObjects (zoneItem.getServiceParameters(), originalSite, createdSite);
460        }
461        else if (zoneItem.getType().equals(ZoneType.CONTENT) && zoneItem instanceof ModifiableZoneItem)
462        {
463            Content content = zoneItem.getContent();
464            String path = content.getPath();
465            
466            String originalPath = originalSite.getPath();
467            if (path.startsWith(originalPath))
468            {
469                String relPath = path.substring(originalPath.length() + 1);
470                try
471                {
472                    // Find symmetric object on copied sub-tree
473                    Content child = createdSite.getChild(relPath);
474                    ((ModifiableZoneItem) zoneItem).setContent(child);
475                }
476                catch (UnknownAmetysObjectException e)
477                {
478                    // Nothing
479                }
480            }
481        }
482    }
483    
484    
485    
486    private void _updateReferencesToAmetysObjects (DataHolder dataHolder, TraversableAmetysObject originalAO, TraversableAmetysObject createdAO)
487    {
488        try
489        {
490            for (String dataName : dataHolder.getDataNames())
491            {
492                if (ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(_getDataTypeID(dataHolder, dataName)))
493                {
494                    _updateReferencesToAmetysObjects(dataHolder.getComposite(dataName), originalAO, createdAO);
495                }
496                else if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(_getDataTypeID(dataHolder, dataName)) && dataHolder instanceof ModelAwareDataHolder)
497                {
498                    // Repeaters are only available on model aware data holders
499                    Repeater repeater = ((ModelAwareDataHolder) dataHolder).getRepeater(dataName);
500                    for (RepeaterEntry entry : repeater.getEntries())
501                    {
502                        _updateReferencesToAmetysObjects(entry, originalAO, createdAO);
503                    }
504                }
505                else if (_isAmetysObject(dataHolder, dataName))
506                {
507                    _updateReferenceToAmetysObject(dataHolder, dataName, originalAO, createdAO);
508                }
509            }
510        }
511        catch (UndefinedItemPathException | UnknownDataException | UnknownTypeException | NotUniqueTypeException e)
512        {
513            // The type of the data has not been determined so there is no way to determine if it is a reference to an ametys object
514        }
515        
516    }
517    
518    private void _updateReferenceToAmetysObject (DataHolder dataHolder, String dataName, TraversableAmetysObject originalAO, TraversableAmetysObject createdAO) throws AmetysRepositoryException
519    {
520        if (dataHolder instanceof ModifiableDataHolder)
521        {
522            if (_isDataMultiple(dataHolder, dataName))
523            {
524                String[] ids = _getStringValue(dataHolder, dataName);
525                List<String> newReferences = new ArrayList<>();
526                for (String id : ids)
527                {
528                    String newReference = _getNewReferenceToAmetysObject(originalAO, createdAO, id);
529                    if (newReference != null)
530                    {
531                        newReferences.add(newReference);
532                    }
533                    else
534                    {
535                        newReferences.add(id);
536                    }
537                }
538                
539                _setStringValue((ModifiableDataHolder) dataHolder, dataName, newReferences.toArray(new String[newReferences.size()]));
540            }
541            else
542            {
543                String id = _getStringValue(dataHolder, dataName);
544                String newReference = _getNewReferenceToAmetysObject(originalAO, createdAO, id);
545                if (newReference != null)
546                {
547                    _setStringValue((ModifiableDataHolder) dataHolder, dataName, newReference);
548                }
549            }
550        }
551    }
552
553    private String _getNewReferenceToAmetysObject(TraversableAmetysObject originalAO, TraversableAmetysObject createdAO, String id)
554    {
555        AmetysObject ametysObject = _resolver.resolveById(id);
556        String path = ametysObject.getPath();
557        
558        String originalPath = originalAO.getPath();
559        if (path.startsWith(originalPath + "/"))
560        {
561            String relPath = path.substring(originalPath.length() + 1);
562            try
563            {
564                // Find symmetric object on copied sub-tree
565                AmetysObject child = createdAO.getChild(relPath);
566                return child.getId();
567            }
568            catch (UnknownAmetysObjectException e)
569            {
570                getLogger().warn("Object of path " + relPath + " was not found on copied sub-tree " + createdAO.getPath(), e);
571                return null;
572            }
573            catch (AmetysRepositoryException e)
574            {
575                getLogger().error("Unable to retrieve object of path " + relPath + " on copied sub-tree " + createdAO.getPath(), e);
576                return null;
577            }
578        }
579        else
580        {
581            return null;
582        }
583    }
584    
585    @SuppressWarnings("static-access")
586    private boolean _isAmetysObject (DataHolder dataHolder, String dataName)
587    {
588        try
589        {
590            if (ModelItemTypeConstants.STRING_TYPE_ID.equals(_getDataTypeID(dataHolder, dataName)))
591            {
592                if (_isDataMultiple(dataHolder, dataName))
593                {
594                    String[] values = _getStringValue(dataHolder, dataName);
595                    for (String value : values)
596                    {
597                        if (_resolver.hasAmetysObjectForId(value))
598                        {
599                            return true;
600                        }
601                    }
602                    
603                    // None of the value of the multiple data is a reference
604                    return false;
605                }
606                else
607                {
608                    String value = _getStringValue(dataHolder, dataName);
609                    return _resolver.hasAmetysObjectForId(value);
610                }
611            }
612            else
613            {
614                return false;
615            }
616        }
617        catch (AmetysRepositoryException | UndefinedItemPathException | UnknownTypeException | NotUniqueTypeException e)
618        {
619            return false;
620        }
621    }
622    
623    private String _getDataTypeID(DataHolder dataHolder, String dataName) throws UndefinedItemPathException, UnknownDataException, UnknownTypeException, NotUniqueTypeException
624    {
625        return dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getType(dataName).getId() : ((ModelLessDataHolder) dataHolder).getType(dataName).getId();
626    }
627    
628    private boolean _isDataMultiple(DataHolder dataHolder, String dataName)
629    {
630        return dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).isMultiple(dataName) : ((ModelLessDataHolder) dataHolder).isMultiple(dataName);
631    }
632    
633    private <T> T _getStringValue(DataHolder dataHolder, String dataName)
634    {
635        return dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getValue(dataName) : ((ModelLessDataHolder) dataHolder).getValue(dataName);
636    }
637    
638    private void _setStringValue(ModifiableDataHolder dataHolder, String dataName, Object value)
639    {
640        if (dataHolder instanceof ModifiableModelAwareDataHolder)
641        {
642            ((ModifiableModelAwareDataHolder) dataHolder).setValue(dataName, value);
643        }
644        else
645        {
646            ((ModifiableModelLessDataHolder) dataHolder).setValue(dataName, value);
647        }
648    }
649    
650    private void _reinitWorkflow (WorkflowAwareContent content) throws AmetysRepositoryException
651    {
652        try
653        {
654            long wId = content.getWorkflowId();
655            
656            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
657            String workflowName = workflow.getWorkflowName(wId);
658            
659            // 1 - Delete the cloned workflow
660            WorkflowAwareContentHelper.removeWorkflowId(content);
661            
662            // For legacy purpose, delete the workflow reference property if exists (only for contents created on 3.x versions)
663            Node node = content.getNode();
664            try
665            {
666                if (node.hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowRef"))
667                {
668                    node.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowRef").remove();
669                }
670            }
671            catch (RepositoryException e)
672            {
673                throw new AmetysRepositoryException("Unable to remove workflowId property", e);
674            }
675            
676            workflow.removeWorkflow(wId);
677            
678            // 2 - Initialize new workflow instance
679            HashMap<String, Object> inputs = new HashMap<>();
680            long workflowId = workflow.initialize(workflowName, 0, inputs);
681            content.setWorkflowId(workflowId);
682            
683            // Update current step property
684            Step currentStep = (Step) workflow.getCurrentSteps(workflowId).iterator().next();
685            content.setCurrentStepId(currentStep.getStepId());
686        }
687        catch (Exception e)
688        {
689            throw new AmetysRepositoryException("Unable to initialize workflow for content " + content.getId(), e);
690        }
691        
692    }
693}