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