001/*
002 *  Copyright 2019 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.ose.export.utils;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.List;
021import java.util.Objects;
022import java.util.Optional;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.cms.data.ContentValue;
032import org.ametys.cms.repository.Content;
033import org.ametys.odf.ODFHelper;
034import org.ametys.odf.ProgramItem;
035import org.ametys.odf.course.Course;
036import org.ametys.odf.enumeration.OdfReferenceTableEntry;
037import org.ametys.odf.enumeration.OdfReferenceTableHelper;
038import org.ametys.odf.orgunit.OrgUnit;
039import org.ametys.odf.ose.export.OSEConstants;
040import org.ametys.odf.program.AbstractProgram;
041import org.ametys.odf.program.Container;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.AmetysRepositoryException;
044import org.ametys.runtime.model.ModelHelper;
045
046/**
047 * A retriever used like a helper to retrieve some ODF elements from ODF items.
048 */
049public class ElementRetriever implements Component, Serviceable
050{
051    /** Avalon Role */
052    public static final String ROLE = ElementRetriever.class.getName();
053
054    /** The helper for ODF contents */
055    protected ODFHelper _odfHelper;
056    /** The helper for reference tables from ODF */
057    protected OdfReferenceTableHelper _refTableHelper;
058    /** The resolver */
059    protected AmetysObjectResolver _resolver;
060
061    @Override
062    public void service(ServiceManager manager) throws ServiceException
063    {
064        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
065        _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
066        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
067    }
068
069    /**
070     * Retrieve the steps ({@link Container}s of nature 'annee') of a given {@link ProgramItem}
071     * @param programItem The program item
072     * @return the steps ({@link Container}s of nature 'annee') of the given program item
073     */
074    public Set<Container> retrieveSteps(ProgramItem programItem)
075    {
076        if (_mustNotExport(programItem))
077        {
078            return Collections.EMPTY_SET;
079        }
080        
081        return _odfHelper.getParentProgramItems(programItem)
082                .stream()
083                .map(this::_getSelfAsStepOrParentSteps)
084                .flatMap(Set::stream)
085                .collect(Collectors.toSet());
086    }
087    
088    private boolean _mustNotExport(ProgramItem programItem)
089    {
090        if (programItem instanceof Course || programItem instanceof Container)
091        {
092            return ((Content) programItem).getValue(OSEConstants.NO_OSE_EXPORT_ATTRIBUTE_NAME, false, false);
093        }
094        else
095        {
096            return false;
097        }
098    }
099    
100    private Set<Container> _getSelfAsStepOrParentSteps(ProgramItem programItem)
101    {
102        return Optional.of(programItem)
103                .filter(this::_isStep)
104                .map(Container.class::cast)
105                .map(Collections::singleton) // Get programItem if it is a step
106                .orElseGet(() -> retrieveSteps(programItem)); // Otherwise search in the parents
107    }
108
109    /**
110     * Retrieve the {@link OrgUnit}s of a given {@link ProgramItem}
111     * @param programItem The program item
112     * @return the {@link OrgUnit}s of the given program item
113     */
114    public Set<OrgUnit> retrieveOrgUnits(ProgramItem programItem)
115    {
116        return _retrieveOrgUnits(programItem);
117    }
118    
119    private Set<OrgUnit> _retrieveOrgUnits(ProgramItem parent)
120    {
121        Set<OrgUnit> directOrgUnits = _getDirectOrgUnits(parent);
122        return directOrgUnits.isEmpty()
123                ? _retrieveOrgUnitsFromParents(parent)
124                : directOrgUnits;
125    }
126    
127    private Set<OrgUnit> _retrieveOrgUnitsFromParents(ProgramItem programItem)
128    {
129        return _odfHelper.getParentProgramItems(programItem)
130                .stream()
131                .map(this::_retrieveOrgUnits)
132                .flatMap(Set::stream)
133                .collect(Collectors.toSet());
134    }
135    
136    private Set<OrgUnit> _getDirectOrgUnits(ProgramItem programItem)
137    {
138        if (programItem instanceof Course)
139        {
140            List<String> orgUnitIds = ((Course) programItem).getOrgUnits();
141            return _resolveAll(orgUnitIds);
142        }
143        else if (programItem instanceof AbstractProgram)
144        {
145            List<String> orgUnitIds = ((AbstractProgram) programItem).getOrgUnits();
146            return _resolveAll(orgUnitIds);
147        }
148        else if (programItem instanceof Container)
149        {
150            return Optional.of(programItem)
151                    .map(Container.class::cast)
152                    .map(Container::getOrgUnit)
153                    .filter(Objects::nonNull)
154                    .map(this::_resolve)
155                    .filter(Objects::nonNull)
156                    .map(Collections::singleton)
157                    .orElseGet(Collections::emptySet);
158        }
159        return Collections.EMPTY_SET;
160    }
161    
162    private Set<OrgUnit> _resolveAll(Collection<String> orgUnitIds)
163    {
164        return orgUnitIds
165                .stream()
166                .map(this::_resolve)
167                .filter(Objects::nonNull)
168                .collect(Collectors.toSet());
169    }
170    
171    private OrgUnit _resolve(String id)
172    {
173        try
174        {
175            return _resolver.resolveById(id);
176        }
177        catch (AmetysRepositoryException e)
178        {
179            // Silently fail
180            return null;
181        }
182    }
183
184    /**
185     * Retrieve the degrees of a given {@link Container}
186     * @param container The container
187     * @return the degrees of the given container
188     */
189    public Set<OdfReferenceTableEntry> retrieveDegree(Container container)
190    {
191        return container.getRootPrograms()
192                .stream()
193                .map(p -> p.<ContentValue>getValue(AbstractProgram.DEGREE))
194                .filter(Objects::nonNull)
195                .map(ContentValue::getContentIfExists)
196                .filter(Optional::isPresent)
197                .map(Optional::get)
198                .map(OdfReferenceTableEntry::new)
199                .collect(Collectors.toSet());
200    }
201
202    /**
203     * Get the potential steps holder of the program item. A step is a container of year type and it can be set manually on intermediate courses.
204     * @param programItem The {@link ProgramItem} on which we have to retrieve the potential steps holder
205     * @return the potential steps holder of the program item
206     */
207    public Set<Container> retrieveStepsHolder(ProgramItem programItem)
208    {
209        return _getStepHolderFromCourse(programItem) // If the element is a course and has a step holder
210            .or(() -> _getStepHolderFromContainer(programItem)) // If the element is a step (container of type year)
211            .map(Collections::singleton)
212            .orElseGet(() -> _getStepsHolderFromParentElements(programItem)); // In all other cases, search in the parent elements
213    }
214    
215    private Optional<Container> _getStepHolderFromCourse(ProgramItem programItem)
216    {
217        return Optional.of(programItem)
218                .filter(Course.class::isInstance)
219                .map(Course.class::cast)
220                .map(c -> c.<ContentValue>getValue("etapePorteuse"))
221                .flatMap(ContentValue::getContentIfExists)
222                .map(Container.class::cast);
223    }
224    
225    private Optional<Container> _getStepHolderFromContainer(ProgramItem programItem)
226    {
227        return Optional.of(programItem)
228                .filter(this::_isStep)
229                .map(Container.class::cast);
230    }
231
232    private boolean _isStep(ProgramItem programItem)
233    {
234        return _isContainerOfNature(programItem, "annee");
235    }
236    
237    private Set<Container> _getStepsHolderFromParentElements(ProgramItem programItem)
238    {
239        return _odfHelper.getParentProgramItems(programItem)
240                .stream()
241                .map(this::retrieveStepsHolder)
242                .flatMap(Set::stream)
243                .collect(Collectors.toSet());
244    }
245    
246    /**
247     * Get the potential period types of the program item. It can be retrieved on courses or containers. The algorithm doesn't search in the parent of semester containers.
248     * @param programItem The {@link ProgramItem} on which we have to retrieve the potential period types
249     * @return the potential period types of the program item
250     */
251    public Set<OdfReferenceTableEntry> retrievePeriodTypes(ProgramItem programItem)
252    {
253        return _getPeriodType(programItem) // Try to get the period type on the current program item
254            .map(Collections::singleton)
255            .orElseGet(() -> _searchPeriodTypesInParents(programItem)); // Or search in the parent program items
256    }
257
258    private Set<OdfReferenceTableEntry> _searchPeriodTypesInParents(ProgramItem programItem)
259    {
260        if (_isSemester(programItem))
261        {
262            return Collections.EMPTY_SET;
263        }
264        
265        // Only if programItem not a semester
266        return _odfHelper.getParentProgramItems(programItem) // Get the direct parent program items
267            .stream()
268            .map(this::retrievePeriodTypes) // Retrieve the period types for all parents
269            .flatMap(Set::stream)
270            .collect(Collectors.toSet());
271    }
272    
273    private Optional<OdfReferenceTableEntry> _getPeriodType(ProgramItem programItem)
274    {
275        return Optional.of(programItem)
276            .map(Content.class::cast) // Cast to Content (ProgramItem is always a Content)
277            .filter(c -> ModelHelper.hasModelItem("period", c.getModel())) // Filter if the attribute "period" is not in the model
278            .map(c -> c.<ContentValue>getValue("period")) // Get the period
279            .flatMap(ContentValue::getContentIfExists)
280            .map(c -> c.<ContentValue>getValue("type")) // Get the period type
281            .flatMap(ContentValue::getContentIfExists)
282            .map(OdfReferenceTableEntry::new);
283    }
284
285    private boolean _isSemester(ProgramItem programItem)
286    {
287        return _isContainerOfNature(programItem, "semestre");
288    }
289
290    private boolean _isContainerOfNature(ProgramItem programItem, String natureCode)
291    {
292        return Optional.of(programItem)
293            .filter(Container.class::isInstance)
294            .map(Container.class::cast)
295            .map(Container::getNature)
296            .map(_refTableHelper::getItemCode)
297            .map(natureCode::equals)
298            .orElse(false);
299    }
300}