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;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Optional;
022import java.util.Set;
023
024import javax.xml.transform.TransformerFactory;
025import javax.xml.transform.dom.DOMResult;
026import javax.xml.transform.sax.SAXTransformerFactory;
027import javax.xml.transform.sax.TransformerHandler;
028
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.xml.XMLUtils;
033import org.apache.commons.lang3.StringUtils;
034import org.w3c.dom.Node;
035import org.w3c.dom.NodeList;
036
037import org.ametys.cms.repository.Content;
038import org.ametys.core.util.dom.AmetysNodeList;
039import org.ametys.odf.enumeration.OdfReferenceTableEntry;
040import org.ametys.odf.enumeration.OdfReferenceTableHelper;
041import org.ametys.odf.orgunit.OrgUnit;
042import org.ametys.odf.orgunit.RootOrgUnitProvider;
043import org.ametys.odf.program.AbstractProgram;
044import org.ametys.odf.program.Program;
045import org.ametys.odf.program.SubProgram;
046import org.ametys.odf.xslt.OdfReferenceTableElement;
047import org.ametys.odf.xslt.ProgramElement;
048import org.ametys.odf.xslt.SubProgramElement;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.plugins.repository.AmetysRepositoryException;
051import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater;
052import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry;
053import org.ametys.runtime.config.Config;
054
055/**
056 * Helper component to be used from XSL stylesheets.
057 */
058public class OdfXSLTHelper implements Serviceable
059{
060    /** The ODF helper */
061    protected static ODFHelper _odfHelper;
062    /** The ODF reference helper */
063    protected static OdfReferenceTableHelper _odfRefTableHelper;
064    /** The Ametys resolver */
065    protected static AmetysObjectResolver _ametysObjectResolver;
066    /** The orgunit root provider */
067    protected static RootOrgUnitProvider _rootOrgUnitProvider;
068    
069    @Override
070    public void service(ServiceManager smanager) throws ServiceException
071    {
072        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
073        _odfRefTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE);
074        _ametysObjectResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
075        _rootOrgUnitProvider = (RootOrgUnitProvider) smanager.lookup(RootOrgUnitProvider.ROLE);
076    }
077    
078    /**
079     * Get the label associated with the degree key
080     * @param cdmValue The code of degree
081     * @return The label of degree or code if not found
082     */
083    public static String degreeLabel (String cdmValue)
084    {
085        return degreeLabel(cdmValue, Config.getInstance().getValue("odf.programs.lang"));
086    }
087    
088    /**
089     * Get the code associated with the given reference table's entry
090     * @param tableRefEntryId The id of entry
091     * @return the code or <code>null</code> if not found
092     */
093    public static String getCode (String tableRefEntryId)
094    {
095        try
096        {
097            Content content = _ametysObjectResolver.resolveById(tableRefEntryId);
098            return content.getValue(OdfReferenceTableEntry.CODE);
099        }
100        catch (AmetysRepositoryException e)
101        {
102            return null;
103        }
104    }
105    
106    /**
107     * Get the id of reference table's entry
108     * @param tableRefId The id of content type
109     * @param code The code
110     * @return the id or <code>null</code> if not found
111     */
112    public static String getEntryId (String tableRefId, String code)
113    {
114        OdfReferenceTableEntry entry = _odfRefTableHelper.getItemFromCode(tableRefId, code);
115        if (entry != null)
116        {
117            return entry.getId();
118        }
119        return null;
120    }
121    
122    /**
123     * Get the label associated with the degree key
124     * @param cdmValue The cdm value of degree
125     * @param lang The language
126     * @return The label of degree or empty string if not found
127     */
128    public static String degreeLabel (String cdmValue, String lang)
129    {
130        return Optional
131            .ofNullable(_odfRefTableHelper.getItemFromCDM(OdfReferenceTableHelper.DEGREE, cdmValue))
132            .map(degree -> degree.getLabel(lang))
133            .orElse(StringUtils.EMPTY);
134    }
135    
136    /**
137     * Get the whole structure of a subprogram, including the structure of child subprograms
138     * @param subprogramId The id of subprogram
139     * @return Node with the subprogram structure
140     */
141    public static Node getSubProgramStructure (String subprogramId)
142    {
143        SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId);
144        return new SubProgramElement(subProgram, _ametysObjectResolver);
145    }
146    
147    /**
148     * Get the structure of a subprogram, including the structure of child subprograms until the given depth
149     * @param subprogramId The id of subprogram
150     * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms.
151     * @return Node with the subprogram structure
152     */
153    public static Node getSubProgramStructure (String subprogramId, int depth)
154    {
155        SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId);
156        return new SubProgramElement(subProgram, depth, null, _ametysObjectResolver);
157    }
158    
159    /**
160     * Get the parent program information 
161     * @param subprogramId The id of subprogram
162     * @return a node for each program's information
163     */
164    public static NodeList getParentProgram (String subprogramId)
165    {
166        return getParentProgramStructure(subprogramId, 0);
167    }
168    
169    /**
170     * Get the certification label of a {@link AbstractProgram}.
171     * Returns null if the program is not certified.
172     * @param abstractProgramId the id of program or subprogram
173     * @return the certification label
174     */
175    public static String getCertificationLabel(String abstractProgramId)
176    {
177        AbstractProgram abstractProgram = _ametysObjectResolver.resolveById(abstractProgramId);
178        if (abstractProgram.isCertified())
179        {
180            String degreeId = null;
181            if (abstractProgram instanceof Program)
182            {
183                degreeId = abstractProgram.getDegree();
184            }
185            else if (abstractProgram instanceof SubProgram)
186            {
187                // Get degree from parent
188                Set<Program> rootPrograms = abstractProgram.getRootPrograms();
189                if (rootPrograms.size() > 0)
190                {
191                    degreeId = rootPrograms.iterator().next().getDegree();
192                }
193            }
194            
195            if (StringUtils.isNotEmpty(degreeId))
196            {
197                Content degree = _ametysObjectResolver.resolveById(degreeId);
198                return degree.getValue("certificationLabel");
199            }
200        }
201        
202        return null;
203    }
204    
205    /**
206     * Get the program information 
207     * @param programId The id of program
208     * @return Node with the program's information
209     */
210    public static Node getProgram (String programId)
211    {
212        return getProgramStructure(programId, 0);
213    }
214    
215    /**
216     * Get the structure of a parent programs, including the structure of child subprograms until the given depth.
217     * @param subprogramId The id of subprogram
218     * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms.
219     * @return a node for each program's structure
220     */
221    public static NodeList getParentProgramStructure (String subprogramId, int depth)
222    {
223        List<ProgramElement> programs = new ArrayList<>();
224        
225        SubProgram subProgram = _ametysObjectResolver.resolveById(subprogramId);
226        
227        Set<Program> rootPrograms = subProgram.getRootPrograms();
228        if (rootPrograms.size() > 0)
229        {
230            programs.add(new ProgramElement(rootPrograms.iterator().next(), depth, null, _ametysObjectResolver));
231        }
232        
233        return new AmetysNodeList(programs);
234    }
235    
236    /**
237     * Get the structure of a program until the given depth.
238     * @param programId The id of program
239     * @param depth Set a positive number to get structure of child subprograms until given depth. Set a negative number to get the whole structure recursively, including the structure of child subprograms. This parameter concerns only subprograms.
240     * @return Node with the program structure
241     */
242    public static Node getProgramStructure (String programId, int depth)
243    {
244        Program program = _ametysObjectResolver.resolveById(programId);
245        return new ProgramElement(program, depth, null, _ametysObjectResolver);
246    }
247    
248    /**
249     * Get the items of a reference table
250     * @param tableRefId the id of reference table
251     * @param lang the language to use for labels
252     * @return the items
253     */
254    public static Node getTableRefItems(String tableRefId, String lang)
255    {
256        return getTableRefItems(tableRefId, lang, false);
257    }
258    
259    /**
260     * Get the items of a reference table
261     * @param tableRefId the id of reference table
262     * @param lang the language to use for labels
263     * @param ordered true to sort items by 'order' attribute
264     * @return the items
265     */
266    public static Node getTableRefItems(String tableRefId, String lang, boolean ordered)
267    {
268        return new OdfReferenceTableElement(tableRefId, _odfRefTableHelper, lang, ordered);
269    }
270    
271    /**
272     * Get the id of root orgunit
273     * @return The id of root
274     */
275    public static String getRootOrgUnitId()
276    {
277        return _rootOrgUnitProvider.getRootId();
278    }
279    
280    /**
281     * Get the id of the first orgunit matching the given UAI code
282     * @param uaiCode the UAI code
283     * @return the id of orgunit or null if not found
284     */
285    public static String getOrgUnitIdByUAICode(String uaiCode)
286    {
287        return Optional.ofNullable(uaiCode)
288                .map(_odfHelper::getOrgUnitByUAICode)
289                .map(OrgUnit::getId)
290                .orElse(null);
291    }
292    
293    /**
294     * Get the more recent educational booklet for one subprogram
295     * @param subProgramId the subprogram id
296     * @return the pdf as an ametys node list
297     */
298    public static AmetysNodeList getEducationalBooklet(String subProgramId)
299    {
300        try
301        {
302            SubProgram subProgram = _ametysObjectResolver.resolveById(subProgramId);
303            if (subProgram.hasValue("educational-booklets"))
304            {
305                ModifiableModelAwareRepeater repeater = subProgram.getRepeater("educational-booklets");
306                ZonedDateTime dateToCompare = null;
307                ModifiableModelAwareRepeaterEntry entry = null;
308                for (ModifiableModelAwareRepeaterEntry repeaterEntry : repeater.getEntries())
309                {
310                    ZonedDateTime date = repeaterEntry.getValue("date");
311                    if (dateToCompare == null || date.isAfter(dateToCompare))
312                    {
313                        dateToCompare = date;
314                        entry = repeaterEntry;
315                    }
316                }
317                
318                if (entry != null)
319                {
320                    SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance();
321                    TransformerHandler th = saxTransformerFactory.newTransformerHandler();
322                    
323                    DOMResult result = new DOMResult();
324                    th.setResult(result);
325                    
326                    th.startDocument();
327                    XMLUtils.startElement(th, "value");
328                    if (entry.hasValue("pdf"))
329                    {
330                        subProgram.dataToSAX(th, "educational-booklets[" + entry.getPosition() + "]/pdf");
331                    }
332                    XMLUtils.endElement(th, "value");
333                    th.endDocument();
334                    
335                    List<Node> values = new ArrayList<>();
336                    
337                    // #getChildNodes() returns a NodeList that contains the value(s) saxed 
338                    // we cannot returns directly this NodeList because saxed values should be wrapped into a <value> tag.
339                    NodeList childNodes = result.getNode().getFirstChild().getChildNodes(); 
340                    for (int i = 0; i < childNodes.getLength(); i++)
341                    {
342                        Node n = childNodes.item(i);
343                        values.add(n);
344                    }
345                    
346                    return new AmetysNodeList(values);
347                }
348            }
349        }
350        catch (Exception e) 
351        {
352            return null;
353        }
354        
355        return null;
356    }
357
358    /**
359     * Count the hours accumulation in the {@link ProgramItem}
360     * @param contentId The id of the {@link ProgramItem}
361     * @return The hours accumulation
362     */
363    public static Double getCumulatedHours(String contentId)
364    {
365        return _odfHelper.getCumulatedHours(_ametysObjectResolver.<ProgramItem>resolveById(contentId));
366    }
367}