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.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.cms.data.ContentValue;
031import org.ametys.cms.repository.Content;
032import org.ametys.odf.ProgramItem;
033import org.ametys.odf.course.Course;
034import org.ametys.odf.enumeration.OdfReferenceTableEntry;
035import org.ametys.odf.enumeration.OdfReferenceTableHelper;
036import org.ametys.odf.orgunit.OrgUnit;
037import org.ametys.odf.ose.export.OSEConstants;
038import org.ametys.odf.ose.export.impl.odf.LogUtils;
039import org.ametys.odf.program.AbstractProgram;
040import org.ametys.odf.program.Container;
041import org.ametys.plugins.odfpilotage.helper.PilotageHelper;
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 extends PilotageHelper
050{
051    /** Avalon Role */
052    @SuppressWarnings("hiding")
053    public static final String ROLE = ElementRetriever.class.getName();
054    
055    /** The cache id for step holders by program item */
056    private static final String __STEP_HOLDERS_BY_ITEM_CACHE_ID = ElementRetriever.class.getName() + "$stepHoldersByItem";
057    
058    /** The resolver */
059    protected AmetysObjectResolver _resolver;
060
061    /** The helper for reference tables from ODF */
062    protected OdfReferenceTableHelper _refTableHelper; 
063     
064    @Override
065    public void service(ServiceManager manager) throws ServiceException
066    {
067        super.service(manager);
068        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
069        _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
070    }
071
072    @Override
073    protected String _getStepsHolderByItemCacheId()
074    {
075        // The cache id is different than its superclass
076        return __STEP_HOLDERS_BY_ITEM_CACHE_ID;
077    }
078    
079    @Override
080    protected Set<Container> _getStepsToCache(ProgramItem programItem, String yearId)
081    {
082        return _mustNotExport(programItem) ? Collections.EMPTY_SET : super._getStepsToCache(programItem, yearId);
083    }
084    
085    private boolean _mustNotExport(ProgramItem programItem)
086    {
087        if (programItem instanceof Course || programItem instanceof Container)
088        {
089            return programItem.getValueOrDefault(OSEConstants.NO_OSE_EXPORT_ATTRIBUTE_NAME, false);
090        }
091        else
092        {
093            return false;
094        }
095    }
096    
097    /**
098     * Retrieve the {@link OrgUnit}s of a given {@link ProgramItem}
099     * @param programItem The program item
100     * @return the {@link OrgUnit}s of the given program item
101     */
102    public Set<OrgUnit> retrieveOrgUnits(ProgramItem programItem)
103    {
104        return _retrieveOrgUnits(programItem);
105    }
106    
107    private Set<OrgUnit> _retrieveOrgUnits(ProgramItem parent)
108    {
109        return Optional.of(parent)
110                .map(this::_getDirectOrgUnit)
111                .map(Collections::singleton)
112                .orElseGet(() -> _retrieveOrgUnitsFromParents(parent));
113    }
114    
115    private Set<OrgUnit> _retrieveOrgUnitsFromParents(ProgramItem programItem)
116    {
117        return _odfHelper.getParentProgramItems(programItem)
118                .stream()
119                .map(this::_retrieveOrgUnits)
120                .flatMap(Set::stream)
121                .collect(Collectors.toSet());
122    }
123    
124    private OrgUnit _getDirectOrgUnit(ProgramItem programItem)
125    {
126        List<String> orgUnitIds = programItem.getOrgUnits();
127        return _resolveAllAndGetFirst(orgUnitIds, (ProgramItem & Content) programItem);
128    }
129    
130    private <T extends ProgramItem & Content> OrgUnit _resolveAllAndGetFirst(Collection<String> orgUnitIds, T programElement)
131    {
132        List<OrgUnit> orgUnits = orgUnitIds.stream()
133                .map(this::_resolve)
134                .filter(Objects::nonNull)
135                .collect(Collectors.toList());
136        
137        if (orgUnits.size() > 1)
138        {
139            LogUtils.programElementWarningOrgUnits(getLogger(), programElement, orgUnits);
140        }
141        
142        return orgUnits.isEmpty() ? null : orgUnits.get(0);
143    }
144    
145    private OrgUnit _resolve(String id)
146    {
147        try
148        {
149            return _resolver.resolveById(id);
150        }
151        catch (AmetysRepositoryException e)
152        {
153            // Silently fail
154            return null;
155        }
156    }
157
158    /**
159     * Retrieve the degrees of a given {@link Container}
160     * @param container The container
161     * @return the degrees of the given container
162     */
163    public Set<OdfReferenceTableEntry> retrieveDegree(Container container)
164    {
165        return _odfHelper.getParentPrograms(container)
166                .stream()
167                .map(p -> p.<ContentValue>getValue(AbstractProgram.DEGREE))
168                .filter(Objects::nonNull)
169                .map(ContentValue::getContentIfExists)
170                .flatMap(Optional::stream)
171                .map(OdfReferenceTableEntry::new)
172                .collect(Collectors.toSet());
173    }
174
175    /**
176     * 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.
177     * @param programItem The {@link ProgramItem} on which we have to retrieve the potential steps holder
178     * @return the potential steps holder of the program item
179     */
180    public Set<Container> retrieveStepsHolder(ProgramItem programItem)
181    {
182        Optional<String> yearId = _odfHelper.getYearId();
183        if (yearId.isPresent())
184        {
185            return _getStepsHolder(programItem, yearId.get());
186        }
187        
188        return Set.of();
189    }
190    
191    /**
192     * 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.
193     * @param programItem The {@link ProgramItem} on which we have to retrieve the potential period types
194     * @return the potential period types of the program item
195     */
196    public Set<OdfReferenceTableEntry> retrievePeriodTypes(ProgramItem programItem)
197    {
198        return getSemesterId()
199            .map(id -> _retrievePeriodTypes(programItem, id))
200            .orElse(Collections.EMPTY_SET);
201    }
202
203    private Set<OdfReferenceTableEntry> _retrievePeriodTypes(ProgramItem programItem, String semesterId)
204    {
205        return _getPeriodType(programItem) // Try to get the period type on the current program item
206            .map(Collections::singleton)
207            .orElseGet(() -> _searchPeriodTypesInParents(programItem, semesterId)); // Or search in the parent program items
208    }
209    
210    private Set<OdfReferenceTableEntry> _searchPeriodTypesInParents(ProgramItem programItem, String semesterId)
211    {
212        if (_odfHelper.isContainerOfNature(programItem, semesterId))
213        {
214            return Collections.EMPTY_SET;
215        }
216        
217        // Only if programItem not a semester
218        return _odfHelper.getParentProgramItems(programItem) // Get the direct parent program items
219            .stream()
220            .map(parent -> _retrievePeriodTypes(parent, semesterId)) // Retrieve the period types for all parents
221            .flatMap(Set::stream)
222            .collect(Collectors.toSet());
223    }
224    
225    private Optional<OdfReferenceTableEntry> _getPeriodType(ProgramItem programItem)
226    {
227        return Optional.of(programItem)
228            .map(Content.class::cast) // Cast to Content (ProgramItem is always a Content)
229            .filter(c -> ModelHelper.hasModelItem("period", c.getModel())) // Filter if the attribute "period" is not in the model
230            .map(c -> c.<ContentValue>getValue("period")) // Get the period
231            .flatMap(ContentValue::getContentIfExists)
232            .map(c -> c.<ContentValue>getValue("type")) // Get the period type
233            .flatMap(ContentValue::getContentIfExists)
234            .map(OdfReferenceTableEntry::new);
235    }
236
237    
238    /**
239     * Get the semester container nature identifier.
240     * @return an {@link Optional} of the semester identifier
241     */
242    public Optional<String> getSemesterId()
243    {
244        return Optional.of(_refTableHelper)
245                .map(rth -> rth.getItemFromCode(OdfReferenceTableHelper.CONTAINER_NATURE, "semestre"))
246                .map(OdfReferenceTableEntry::getId)
247                .filter(StringUtils::isNotBlank);
248    }
249}