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