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