001/*
002 *  Copyright 2019 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.odf.catalog;
017
018import java.util.Map;
019import java.util.Objects;
020import java.util.Optional;
021import java.util.stream.Stream;
022
023import org.apache.commons.lang3.StringUtils;
024
025import org.ametys.cms.contenttype.ContentAttributeDefinition;
026import org.ametys.cms.data.ContentValue;
027import org.ametys.cms.repository.Content;
028import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
029import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareComposite;
030import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater;
031import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry;
032import org.ametys.plugins.repository.model.CompositeDefinition;
033import org.ametys.plugins.repository.model.RepeaterDefinition;
034import org.ametys.runtime.model.ModelItem;
035import org.ametys.runtime.plugin.component.AbstractLogEnabled;
036
037/**
038 * The abstract class to copy content attribute of program item type.
039 */
040public abstract class AbstractProgramItemAttributeCopyUpdater extends AbstractLogEnabled implements CopyCatalogUpdater
041{
042    /**
043     * Update the program item attribute with the value of the new catalog.
044     * @param dataHolder The data holder
045     * @param definitionPath The definition path
046     * @param copiedContents The copied contents for the referenced attribute
047     */
048    protected void _updateContentAttribute(ModifiableModelAwareDataHolder dataHolder, String definitionPath, Map<Content, Content> copiedContents)
049    {
050        String[] pathSegments = StringUtils.split(definitionPath, ModelItem.ITEM_PATH_SEPARATOR);
051        String attributeName = pathSegments[0];
052        ModelItem definition = dataHolder.getDefinition(attributeName);
053        if (pathSegments.length == 1 && definition instanceof ContentAttributeDefinition)
054        {
055            if (((ContentAttributeDefinition) definition).isMultiple())
056            {
057                _updateMultipleContentAttribute(dataHolder, attributeName, copiedContents);
058            }
059            else
060            {
061                _updateSingleContentAttribute(dataHolder, attributeName, copiedContents);
062            }
063        }
064        else if (definition instanceof RepeaterDefinition)
065        {
066            ModifiableModelAwareRepeater repeater = dataHolder.getRepeater(attributeName);
067            if (repeater != null)
068            {
069                String childDefinitionPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length);
070                for (ModifiableModelAwareRepeaterEntry entry : repeater.getEntries())
071                {
072                    _updateContentAttribute(entry, childDefinitionPath, copiedContents);
073                }
074            }
075        }
076        else if (definition instanceof CompositeDefinition)
077        {
078            ModifiableModelAwareComposite composite = dataHolder.getComposite(attributeName);
079            if (composite != null)
080            {
081                String childDefinitionPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length);
082                _updateContentAttribute(composite, childDefinitionPath, copiedContents);
083            }
084        }
085        else
086        {
087            // This method shouldn't be called with anything else than a final content attribute
088            throw new IllegalArgumentException("The path '" + definitionPath + "' on data holder '" + definition.getPath() + "' is not a content attribute on content type '" + definition.getModel().getId() + "'.");
089        }
090    }
091
092    /**
093     * Update the single attribute with the content value.
094     * @param dataHolder The data holder
095     * @param attributeName The attribute name
096     * @param copiedContents The copied contents for the referenced attribute
097     */
098    protected void _updateSingleContentAttribute(ModifiableModelAwareDataHolder dataHolder, String attributeName, Map<Content, Content> copiedContents)
099    {
100        Content newContent = Optional.of(dataHolder)
101            // Get the attribute value
102            .map(dh -> dh.<ContentValue>getValue(attributeName))
103            // Get the copied content
104            .map(initialValue -> _getValueFromCopiedContents(initialValue, copiedContents))
105            // Get the retrieved value or null
106            .orElse(null);
107
108        // Set the attribute with the new content
109        // If null, the attribute value will be empty
110        dataHolder.setValue(attributeName, newContent);
111    }
112    
113    /**
114     * Update the multiple attribute with the program item value.
115     * @param dataHolder The data holder
116     * @param attributeName The attribute name
117     * @param copiedContents The copied contents for the referenced attribute
118     */
119    protected void _updateMultipleContentAttribute(ModifiableModelAwareDataHolder dataHolder, String attributeName, Map<Content, Content> copiedContents)
120    {
121        Content[] newContents = Optional.of(dataHolder)
122            // Get the attribute values
123            .map(dh -> dh.<ContentValue[]>getValue(attributeName))
124            // Build a stream from the ContentValue array
125            .map(Stream::of)
126            // Or build an empty stream if there is no values
127            .orElseGet(Stream::empty)
128            // For each element, get the copied content
129            .map(initialValue -> _getValueFromCopiedContents(initialValue, copiedContents))
130            .filter(Objects::nonNull)
131            // Collect into an array
132            .toArray(Content[]::new);
133        
134        // Set the attribute with the new contents list
135        dataHolder.setValue(attributeName, newContents);
136    }
137    
138    private Content _getValueFromCopiedContents(ContentValue initialContent, Map<Content, Content> copiedContents)
139    {
140        Content copiedContent = initialContent.getContentIfExists()
141            // Keep himself if not found in the copied contents map
142            .map(content -> copiedContents.getOrDefault(content, content))
143            .orElse(null);
144        
145        if (getLogger().isDebugEnabled())
146        {
147            if (copiedContent == null)
148            {
149                getLogger().debug("The initial content '{}' does not exists anymore, it cannot match a copied content.", initialContent.getContentId());
150            }
151            else if (copiedContent.getId().equals(initialContent.getContentId()))
152            {
153                getLogger().debug("The initial content '{}' does not match any copied content.", initialContent.getContentId());
154            }
155        }
156        
157        return copiedContent;
158    }
159}