001/*
002 *  Copyright 2015 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.program;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Map.Entry;
021import java.util.Set;
022
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027
028import org.ametys.cms.ObservationConstants;
029import org.ametys.cms.repository.Content;
030import org.ametys.cms.repository.ModifiableContent;
031import org.ametys.cms.repository.WorkflowAwareContent;
032import org.ametys.cms.rights.ContentRightAssignmentContext;
033import org.ametys.cms.workflow.ContentWorkflowHelper;
034import org.ametys.core.observation.Event;
035import org.ametys.core.observation.ObservationManager;
036import org.ametys.core.ui.Callable;
037import org.ametys.core.user.CurrentUserProvider;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.odf.ODFHelper;
040import org.ametys.odf.observation.OdfObservationConstants;
041import org.ametys.odf.translation.TranslationHelper;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.UnknownAmetysObjectException;
044
045import com.opensymphony.workflow.WorkflowException;
046
047/**
048 * DAO for manipulating ODF programs.
049 *
050 */
051public class ProgramDAO implements Serviceable, Component
052{
053    /** The Avalon role */
054    public static final String ROLE = ProgramDAO.class.getName();
055    
056    /** The ametys object resolver */
057    protected AmetysObjectResolver _resolver;
058    /** Observer manager. */
059    protected ObservationManager _observationManager;
060    /** The current user provider. */
061    protected CurrentUserProvider _currentUserProvider;
062    /** ODF helper */
063    protected ODFHelper _odfHelper;
064    /** The content workflow helper */
065    protected ContentWorkflowHelper _contentWorkflowHelper;
066    /** The program translation updater extension point */
067    protected ProgramTranslationUpdaterExtensionPoint _programTranslationUpdaterExtensionPoint;
068    
069    @Override
070    public void service(ServiceManager manager) throws ServiceException
071    {
072        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
073        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
074        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
075        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
076        _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE);
077        _programTranslationUpdaterExtensionPoint = (ProgramTranslationUpdaterExtensionPoint) manager.lookup(ProgramTranslationUpdaterExtensionPoint.ROLE);
078    }
079    
080    /**
081     * Translates a given program in a language
082     * @param contentId The program id
083     * @param language The language in which to translate
084     * @param fullCopy True if full copy
085     * @return A Map with id of translated program or an error message
086     * @throws Exception if an error occurs
087     */
088    @Callable (rights = "TranslationFlagging_Rights_Set", paramIndex = 0, rightContext = ContentRightAssignmentContext.ID)
089    public Map<String, String> translateProgram (String contentId, String language, boolean fullCopy) throws Exception
090    {
091        Map<String, String> result = new HashMap<>();
092        
093        Program program = _resolver.resolveById(contentId);
094        
095        if (program.getLanguage().equals(language))
096        {
097            result.put("error", "same-language");
098            return result;
099        }
100        
101        Map<Content, Content> translatedContents = new HashMap<>();
102        String translatedProgramId = null;
103        
104        // Get existing translations
105        Map<String, String> translations = TranslationHelper.getTranslations(program);
106        
107        if (!translations.containsKey(language))
108        {
109            translatedProgramId = copyProgram(program, language, fullCopy, translatedContents);
110        }
111        else
112        {
113            translatedProgramId = translations.get(language);
114            // Check if content exists
115            try
116            {
117                _resolver.resolveById(translatedProgramId);
118                result.put("error", "already-exists");
119            }
120            catch (UnknownAmetysObjectException e) 
121            {
122                translatedProgramId = copyProgram(program, language, fullCopy, translatedContents);
123            }
124        }
125        
126        linkTranslations(translatedContents);
127        
128        // Add the workflow step, send notifications and extract outgoing references
129        for (Content newContent : translatedContents.values())
130        {
131            if (newContent instanceof WorkflowAwareContent newWorkflowAwareContent)
132            {
133                _contentWorkflowHelper.doAction(newWorkflowAwareContent, getCopyActionId());
134            }
135        }
136        
137        result.put("translated-program-id", translatedProgramId);
138        
139        return result;
140    }
141
142    /**
143     * Copy the given {@link Program} for translation
144     * @param program The program to copy
145     * @param language The language in which to translate
146     * @param fullCopy Set to <code>true</code> to copy the sub-structure
147     * @param copiedContents the initial contents with their copied content
148     * @return The created content identifier
149     * @throws WorkflowException If an error occurred
150     */
151    protected String copyProgram(Program program, String language, boolean fullCopy, Map<Content, Content> copiedContents) throws WorkflowException
152    {
153        Program copiedProgram = _odfHelper.copyProgramItem(program, null, language, null, fullCopy, copiedContents);
154        
155        Set<String> copyUpdaters = _programTranslationUpdaterExtensionPoint.getExtensionsIds();
156        for (String updaterId : copyUpdaters)
157        {
158            // Call updaters after copy of program
159            ProgramTranslationUpdater updater = _programTranslationUpdaterExtensionPoint.getExtension(updaterId);
160            updater.updateContents(null, null, copiedContents, null);
161        }
162        
163        return copiedProgram.getId();
164    }
165    
166    /**
167     * Store links to the other translations for all copied contents
168     * @param translatedContents The translated contents
169     */
170    protected void linkTranslations(Map<Content, Content> translatedContents)
171    {
172        for (Entry<Content, Content> entry : translatedContents.entrySet())
173        {
174            ModifiableContent originalContent = (ModifiableContent) entry.getKey();
175            ModifiableContent translatedContent = (ModifiableContent) entry.getValue();
176            
177            linkTranslations(originalContent, translatedContent);
178            
179            originalContent.saveChanges();
180            translatedContent.saveChanges();
181            
182            Map<String, Object> eventParams = new HashMap<>();
183            eventParams.put(ObservationConstants.ARGS_CONTENT, originalContent);
184            
185            _observationManager.notify(new Event(OdfObservationConstants.ODF_CONTENT_TRANSLATED, _getCurrentUser(), eventParams));
186        }
187    }
188    
189    /**
190     * Store links to the other translations in all the translated objects.
191     * @param originalContent The original content
192     * @param translatedContent The translated content
193     */
194    protected void linkTranslations(ModifiableContent originalContent, ModifiableContent translatedContent)
195    {
196        Map<String, String> translations = TranslationHelper.getTranslations(originalContent);
197        
198        // Add the original and translated references.
199        translations.put(originalContent.getLanguage(), originalContent.getId());
200        translations.put(translatedContent.getLanguage(), translatedContent.getId());
201        
202        for (String contentId : translations.values())
203        {
204            ModifiableContent content = _resolver.resolveById(contentId);
205            
206            Map<String, String> otherTranslations = new HashMap<>(translations);
207            otherTranslations.remove(content.getLanguage());
208            
209            TranslationHelper.setTranslations(content, otherTranslations);
210        }
211    }
212    
213    /**
214     * Provides the current user.
215     * @return the identity which cannot be <code>null</code>.
216     */
217    protected UserIdentity _getCurrentUser()
218    {
219        return _currentUserProvider.getUser();
220    }
221    
222    /**
223     * Get the workflow action id for copy.
224     * @return The workflow action id
225     */
226    protected int getCopyActionId()
227    {
228        return 210;
229    }
230}