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