001/*
002 *  Copyright 2013 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.copy;
017
018import java.util.HashMap;
019import java.util.Locale;
020import java.util.Map;
021import java.util.Map.Entry;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.commons.lang3.StringUtils;
026
027import org.ametys.cms.content.CopyContentComponent;
028import org.ametys.cms.content.CopyReport;
029import org.ametys.cms.content.CopyReport.CopyMode;
030import org.ametys.cms.contenttype.ContentTypesHelper;
031import org.ametys.cms.repository.Content;
032import org.ametys.cms.repository.ModifiableContent;
033import org.ametys.cms.workflow.CreateContentFunction;
034import org.ametys.cms.workflow.EditContentFunction;
035import org.ametys.core.util.I18nUtils;
036import org.ametys.plugins.repository.UnknownAmetysObjectException;
037import org.ametys.plugins.workflow.AbstractWorkflowComponent;
038import org.ametys.runtime.i18n.I18nizableText;
039
040import com.opensymphony.module.propertyset.PropertySet;
041import com.opensymphony.workflow.WorkflowException;
042
043/**
044 * OSWorkflow function to create a content by copy of another
045 * 
046 * The required transient variables:
047 * - CreateContentByCopyFunction.BASE_CONTENT_KEY - Content The content that will be used for duplication.
048 * - or CreateContentByCopyFunction.BASE_CONTENT_ID - String The id of content The content that will be used for duplication.
049 * 
050 * - CreateContentByCopyFunction.COPY_MAP_KEY - Map<String, Object> The map of properties to copy. Can be null if CreateContentByCopyFunction.COPY_VIEW_NAME is used
051 */
052public class CreateContentByCopyFunction extends CreateContentFunction
053{
054    /** Constant for storing the base content used for the duplication into the transient variables map. */
055    public static final String BASE_CONTENT_KEY = CreateContentByCopyFunction.class.getName() + "$baseContent";
056    
057    /** Constant for storing the id of base content used for the duplication into the transient variables map. */
058    public static final String BASE_CONTENT_ID = CreateContentByCopyFunction.class.getName() + "$baseContentId";
059    
060    /** Constant for storing the map containing the duplication info into the transient variables map. Can be null.*/
061    public static final String COPY_MAP_KEY = CreateContentByCopyFunction.class.getName() + "$copyProperties";
062    
063    /** Constant for storing the copy report object into the transient variables map. Can be null. */
064    public static final String COPY_REPORT_KEY = CreateContentByCopyFunction.class.getName() + "$copyReport";
065    
066    /** Constant for storing the name of view use for copy. */
067    public static final String COPY_VIEW_NAME = CreateContentByCopyFunction.class.getName() + "$viewName";
068    /** Constant for storing the name of fallback view to use for copy. */
069    public static final String COPY_FALLBACK_VIEW_NAME = CreateContentByCopyFunction.class.getName() + "$fallbackViewName";
070
071    /** The content copy component */
072    protected CopyContentComponent _copyContentComponent;
073    /** The content type helper */
074    protected ContentTypesHelper _cTypesHelper;
075
076    /** I18n Utils */
077    protected I18nUtils _i18nUtils;
078
079    @Override
080    public void service(ServiceManager manager) throws ServiceException
081    {
082        super.service(manager);
083        _copyContentComponent = (CopyContentComponent) manager.lookup(CopyContentComponent.ROLE);
084        _cTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
085        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
086    }
087    
088    @SuppressWarnings({"cast", "unchecked"})
089    @Override
090    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
091    {
092        // Preparation of the transientVars to work with the super method (CreateContentFunction).
093        Content baseContent = getBaseContentForCopy(transientVars);
094        
095        if (baseContent == null)
096        {
097            throw new WorkflowException("Unable to retrieve the base content for the duplication.");
098        }
099        
100        if (transientVars.get(CreateContentFunction.CONTENT_TITLE_KEY) == null)
101        {
102            if (_contentHelper.isMultilingual(baseContent))
103            {
104                Map<String, String> titleVariants = new HashMap<>();
105                Map<String, String> baseTitleVariants = _contentHelper.getTitleVariants(baseContent);
106                
107                for (Entry<String, String> entry : baseTitleVariants.entrySet())
108                {
109                    titleVariants.put(entry.getKey(), entry.getValue() + _i18nUtils.translate(new I18nizableText("plugin.cms", "CONTENT_COPY_CREATE_CONTENT_TITLE_SUFFIX")));
110                }
111                
112                String defaultLang = (String) transientVars.get(CreateContentFunction.CONTENT_LANGUAGE_KEY);
113                Locale defaultLocale = StringUtils.isNotEmpty(defaultLang) ? new Locale(defaultLang) : new Locale(titleVariants.keySet().iterator().next());
114                transientVars.put(CreateContentFunction.CONTENT_NAME_KEY, baseContent.getTitle(defaultLocale));
115                transientVars.put(CreateContentFunction.CONTENT_TITLE_VARIANTS_KEY, titleVariants);
116            }
117            else
118            {
119                transientVars.put(CreateContentFunction.CONTENT_NAME_KEY, baseContent.getTitle(null));
120                // If title is not set, the base title is set with a copy suffix. 
121                transientVars.put(CreateContentFunction.CONTENT_TITLE_KEY, baseContent.getTitle(null) + _i18nUtils.translate(new I18nizableText("plugin.cms", "CONTENT_COPY_CREATE_CONTENT_TITLE_SUFFIX")));
122            }
123        }
124        else
125        {
126            transientVars.put(CreateContentFunction.CONTENT_NAME_KEY, (String) transientVars.get(CreateContentFunction.CONTENT_TITLE_KEY));
127        }
128        
129        if (transientVars.get(CreateContentFunction.CONTENT_TYPES_KEY) == null)
130        {
131            transientVars.put(CreateContentFunction.CONTENT_TYPES_KEY, baseContent.getTypes());
132        }
133        
134        transientVars.put(CreateContentFunction.CONTENT_MIXINS_KEY, baseContent.getMixinTypes());
135        transientVars.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, baseContent.getLanguage());
136        
137        // Super method call.
138        super.execute(transientVars, args, ps);
139        
140        ModifiableContent targetContent = (ModifiableContent) getResultsMap(transientVars).get(CONTENT_KEY);
141        
142        // process copy map
143        String viewName = (String) transientVars.getOrDefault(COPY_VIEW_NAME, "default-edition");
144        String fallbackViewName = (String) transientVars.getOrDefault(COPY_FALLBACK_VIEW_NAME, "main");
145        
146        if (!transientVars.containsKey(COPY_REPORT_KEY))
147        {
148            transientVars.put(COPY_REPORT_KEY, new CopyReport(baseContent.getId(), _contentHelper.getTitle(baseContent), true, viewName, fallbackViewName, CopyMode.CREATION));
149        }
150        
151        CopyReport copyReport = (CopyReport) transientVars.get(COPY_REPORT_KEY);
152        
153        Map<String, Object> copyMap = (Map<String, Object>) transientVars.get(COPY_MAP_KEY);
154        Map<String, Object> additionalCopyMap = getAdditionalCopyMap(transientVars, baseContent, viewName, fallbackViewName);
155        
156        // get values from copy map
157        Map<String, Object> values = _copyContentComponent.computeValues(baseContent, targetContent, copyMap, additionalCopyMap, viewName, fallbackViewName, copyReport);
158        
159        // Title attribute must never be copied, it has been set during the content creation.
160        values.remove("title");
161        
162        // process input values for EditContentFunction
163        processValues(transientVars, targetContent, values);
164        
165        Map<String, Object> parameters = new HashMap<>();
166        parameters.put(EditContentFunction.VALUES_KEY, values);
167        parameters.put(EditContentFunction.QUIT, true);
168        transientVars.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, parameters);
169    }
170    
171    /**
172     * Called after creation and before execution of the {@link EditContentFunction} to allow subclasses to add/remove/modify the values.
173     * @param transientVars The workflow transient vars
174     * @param targetContent The newly created content
175     * @param values the values computed from the copyMap and to be given to the EditContentFunction.
176     * @throws WorkflowException if an error occurs
177     */
178    protected void processValues(Map transientVars, ModifiableContent targetContent, Map<String, Object> values) throws WorkflowException
179    {
180        // do nothing
181    }
182    
183    /**
184     * Get the content
185     * @param transientVars The workflow transient vars with BASE_CONTENT_KEY (the content itself) or BASE_CONTENT_ID (the id of the content)
186     * @return the content or null if the object cannot be found
187     */
188    protected Content getBaseContentForCopy (Map transientVars)
189    {
190        Content baseContent = (Content) transientVars.get(BASE_CONTENT_KEY);
191        
192        if (baseContent == null)
193        {
194            String baseContentId = (String) transientVars.get(BASE_CONTENT_ID);
195            
196            try
197            {
198                baseContent = _resolver.resolveById(baseContentId);
199            }
200            catch (UnknownAmetysObjectException e)
201            {
202                return null;
203            }
204        }
205        
206        return baseContent;
207    }
208
209    /**
210     * Get an additional copy map, if any. It allows subclasses to provide additional data to copy.
211     * This map is concatenated to the initial copy map. 
212     * @param transientVars the workflow parameters
213     * @param content the source content to be copied
214     * @param viewName the view name
215     * @param fallbackViewName the fallback view name, if required view does not exist
216     * @return the additional copy map, or null if none.
217     * @throws WorkflowException if an error occurs
218     */
219    protected Map<String, Object> getAdditionalCopyMap(Map transientVars, Content content, String viewName, String fallbackViewName) throws WorkflowException
220    {
221        // the default implementation does not provide any additional copy map
222        return null;
223    }
224}