001/*
002 *  Copyright 2020 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.plugins.odfsync.apogee.scc;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Optional;
022import java.util.Set;
023
024import org.apache.avalon.framework.activity.Initializable;
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.environment.Request;
034import org.slf4j.Logger;
035
036import org.ametys.cms.repository.Content;
037import org.ametys.cms.repository.ContentDAO;
038import org.ametys.cms.repository.ModifiableContent;
039import org.ametys.odf.ODFHelper;
040import org.ametys.odf.ProgramItem;
041import org.ametys.odf.catalog.CatalogsManager;
042import org.ametys.odf.course.Course;
043import org.ametys.odf.coursepart.CoursePart;
044import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO;
045import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionHelper;
046import org.ametys.runtime.config.Config;
047
048/**
049 * Helper for Apogee Synchronizable Contents Collections.
050 */
051public class ApogeeSynchronizableContentsCollectionHelper implements Serviceable, Component, Contextualizable, Initializable
052{
053    /** The Avalon Role */
054    public static final String ROLE = ApogeeSynchronizableContentsCollectionHelper.class.getName();
055 
056    /** Request attribute name to store handled contents during import or synchronization. */
057    public static final String HANDLED_CONTENTS = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$handledContents";
058
059    /** Request attribute name to store catalog during import or synchronization. */
060    public static final String CATALOG = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$catalog";
061    
062    /** Request attribute name to store language during import or synchronization. */
063    public static final String LANG = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$lang";
064    
065    /** SCC DAO */
066    protected SynchronizableContentsCollectionHelper _sccHelper;
067    
068    /** The ODF Helper */
069    protected ODFHelper _odfHelper;
070    
071    /** SCC DAO */
072    protected SynchronizableContentsCollectionDAO _sccDAO;
073    
074    /** Content DAO */
075    protected ContentDAO _contentDAO;
076    
077    /** The catalogs manager */
078    protected CatalogsManager _catalogsManager;
079    
080    /** Context */
081    protected Context _context;
082    
083    /** Default language configured for ODF */
084    protected String _odfLang;
085    
086    
087    @Override
088    public void service(ServiceManager smanager) throws ServiceException
089    {
090        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
091        _sccHelper = (SynchronizableContentsCollectionHelper) smanager.lookup(SynchronizableContentsCollectionHelper.ROLE);
092        _sccDAO = (SynchronizableContentsCollectionDAO) smanager.lookup(SynchronizableContentsCollectionDAO.ROLE);
093        _contentDAO = (ContentDAO) smanager.lookup(ContentDAO.ROLE);
094        _catalogsManager = (CatalogsManager) smanager.lookup(CatalogsManager.ROLE);
095    }
096    
097    public void contextualize(Context context) throws ContextException
098    {
099        _context = context;
100    }
101    
102    public void initialize() throws Exception
103    {
104        _odfLang = Config.getInstance().getValue("odf.programs.lang");
105    }
106    
107    /**
108     * Synchronize the content or its children if the content has no Apogee SCC
109     * @param content the content
110     * @param logger the logger
111     */
112    public void synchronizeContent(ModifiableContent content, Logger logger)
113    {
114        AbstractApogeeSynchronizableContentsCollection scc = getContentSCC(content, logger).orElse(null);
115        // The content has a SCC, so synchronize it
116        if (scc != null)
117        {
118            try
119            {
120                Map<String, Object> searchParams = new HashMap<>();
121                String idField = scc.getIdField();
122                searchParams.put(idField, content.getValue(idField));
123                if (scc.removalSync() && scc.getTotalCount(searchParams, logger) == 0)
124                {
125                    _contentDAO.forceDeleteContentsWithLog(List.of(content), null, logger);
126                }
127                else
128                {
129                    scc.synchronizeContent(content, logger);
130                }
131            }
132            catch (Exception e)
133            {
134                logger.error("An error occurred synchronized content '{}' ({}) from SCC '{}'", content.getTitle(), content.getId(), scc.getId(), e);
135            }
136        }
137        // The content is manually created, so search in deeper children
138        else if (addToHandleContents(content.getId()) && content instanceof ProgramItem programItem)
139        {
140            for (ProgramItem syncChild : _odfHelper.getChildProgramItems(programItem))
141            {
142                synchronizeContent((ModifiableContent) syncChild, logger);
143            }
144            
145            if (content instanceof Course course)
146            {
147                for (CoursePart syncChild : course.getCourseParts())
148                {
149                    synchronizeContent(syncChild, logger);
150                }
151            }
152        }
153    }
154    
155    /**
156     * Get the Apogee SCC for the content if it exists
157     * @param content The content to search on
158     * @param logger the logger
159     * @return the Apogee SCC
160     */
161    public Optional<AbstractApogeeSynchronizableContentsCollection> getContentSCC(Content content, Logger logger)
162    {
163        try
164        {
165            return _sccHelper.getSynchronizableCollectionIds(content)
166                .stream()
167                .map(_sccDAO::getSynchronizableContentsCollection)
168                .filter(AbstractApogeeSynchronizableContentsCollection.class::isInstance)
169                .map(AbstractApogeeSynchronizableContentsCollection.class::cast)
170                .findFirst();
171        }
172        catch (Exception e)
173        {
174            logger.error("An error occurred getting ametys-internal:scc property", e);
175        }
176        
177        return Optional.empty();
178    }
179    
180    /**
181     * Add the content ID to the handle contents list.
182     * @param contentId Content ID
183     * @return <code>true</code> if the content ID have been added, <code>false</code> is returned if the content ID already exists in the handle contents list.
184     */
185    public boolean addToHandleContents(String contentId)
186    {
187        return _addContentToRequestAttribute(contentId, HANDLED_CONTENTS);
188    }
189    
190    private boolean _addContentToRequestAttribute(String contentId, String attributeName)
191    {
192        Request request = ContextHelper.getRequest(_context);
193        @SuppressWarnings("unchecked")
194        Set<String> handleContents = (Set<String>) request.getAttribute(attributeName);
195        boolean added = handleContents.add(contentId);
196        request.setAttribute(attributeName, handleContents);
197        return added;
198    }
199    
200    /**
201     * Get the synchronization catalog, can be the one defined by the synchronized content
202     * or the default catalog.
203     * @return the synchronization catalog, can be null
204     */
205    public String getSynchronizationCatalog()
206    {
207        return Optional.of(ContextHelper.getRequest(_context))
208            .map(r -> (String) r.getAttribute(CATALOG))
209            .orElseGet(() -> _catalogsManager.getDefaultCatalogName());
210    }
211    
212    /**
213     * Get the synchronization language, can be the one defined by the synchronized content
214     * or the ODF language.
215     * @return the synchronization language
216     */
217    public String getSynchronizationLang()
218    {
219        return Optional.of(ContextHelper.getRequest(_context))
220                .map(r -> (String) r.getAttribute(LANG))
221                .orElse(_odfLang);
222    }
223}