001/*
002 *  Copyright 2014 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.export.pdf;
017
018import java.io.IOException;
019import java.net.MalformedURLException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.stream.Stream;
025
026import org.apache.avalon.framework.parameters.ParameterException;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.ProcessingException;
030import org.apache.cocoon.components.source.impl.SitemapSource;
031import org.apache.cocoon.environment.ObjectModelHelper;
032import org.apache.cocoon.generation.ServiceableGenerator;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.contenttype.ContentAttributeDefinition;
039import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
040import org.ametys.cms.repository.Content;
041import org.ametys.cms.repository.ContentTypeExpression;
042import org.ametys.cms.repository.LanguageExpression;
043import org.ametys.core.util.IgnoreRootHandler;
044import org.ametys.core.util.LambdaUtils;
045import org.ametys.odf.NoLiveVersionException;
046import org.ametys.odf.ODFHelper;
047import org.ametys.odf.ProgramItem;
048import org.ametys.odf.catalog.Catalog;
049import org.ametys.odf.catalog.CatalogsManager;
050import org.ametys.odf.enumeration.OdfReferenceTableEntry;
051import org.ametys.odf.enumeration.OdfReferenceTableHelper;
052import org.ametys.odf.orgunit.OrgUnit;
053import org.ametys.odf.program.AbstractProgram;
054import org.ametys.odf.program.Program;
055import org.ametys.odf.program.ProgramFactory;
056import org.ametys.plugins.repository.AmetysObjectIterable;
057import org.ametys.plugins.repository.AmetysObjectResolver;
058import org.ametys.plugins.repository.AmetysRepositoryException;
059import org.ametys.plugins.repository.UnknownAmetysObjectException;
060import org.ametys.plugins.repository.query.QueryHelper;
061import org.ametys.plugins.repository.query.SortCriteria;
062import org.ametys.plugins.repository.query.expression.AndExpression;
063import org.ametys.plugins.repository.query.expression.Expression;
064import org.ametys.plugins.repository.query.expression.Expression.Operator;
065import org.ametys.plugins.repository.query.expression.OrExpression;
066import org.ametys.plugins.repository.query.expression.StringExpression;
067import org.ametys.runtime.model.View;
068
069/**
070 * Generator producing the SAX events for the catalogue summary 
071 */ 
072public class FOProgramsGenerator extends ServiceableGenerator
073{
074    /** The Ametys object resolver */
075    protected AmetysObjectResolver _resolver;
076    /** The content type extension point */
077    protected ContentTypeExtensionPoint _ctypeEP;
078    /** The ODf helper */
079    protected ODFHelper _odfHelper;
080    /** The ODf enumeration helper */
081    protected OdfReferenceTableHelper _odfTableRefHelper;
082    /** The catalog manager */
083    protected CatalogsManager _catalogManager;
084    
085    @Override
086    public void service(ServiceManager sManager) throws ServiceException
087    {
088        super.service(sManager);
089        _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE);
090        _ctypeEP = (ContentTypeExtensionPoint) sManager.lookup(ContentTypeExtensionPoint.ROLE);
091        _odfHelper = (ODFHelper) sManager.lookup(ODFHelper.ROLE);
092        _odfTableRefHelper = (OdfReferenceTableHelper) sManager.lookup(OdfReferenceTableHelper.ROLE);
093        _catalogManager = (CatalogsManager) sManager.lookup(CatalogsManager.ROLE);
094    }
095    
096    public void generate() throws IOException, SAXException, ProcessingException
097    {
098        contentHandler.startDocument();
099        XMLUtils.startElement(contentHandler, "programs");
100        
101        Catalog catalog = null;
102        if ("_default".equals(source))
103        {
104            catalog = _catalogManager.getDefaultCatalog();
105        }
106        else
107        {
108            catalog = _catalogManager.getCatalog(source);
109        }
110        
111        if (catalog == null)
112        {
113            throw new IllegalArgumentException ("Failed to generated PDF of unknown catalog '" + source + "'");
114        }
115
116        String lang;
117        try
118        {
119            lang = parameters.getParameter("lang");
120        }
121        catch (ParameterException e)
122        {
123            throw new IllegalArgumentException ("Missing lang parameter", e);
124        }
125        
126        // Catalog
127        AttributesImpl attrs = new AttributesImpl();
128        attrs.addCDATAAttribute("name", source);
129        XMLUtils.createElement(contentHandler, "catalog", attrs, catalog.getTitle());
130
131        Map parentContext = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
132        
133        // OrgUnit
134        OrgUnit orgUnit = Optional.ofNullable(parentContext)
135                .map(pc -> pc.get("orgunit"))
136                .map(Object::toString)
137                .map(LambdaUtils.wrap(_resolver::<OrgUnit>resolveById))
138                .orElse(null);
139        if (orgUnit != null)
140        {
141            attrs.clear();
142            attrs.addCDATAAttribute("id", orgUnit.getId());
143            attrs.addCDATAAttribute("uaiCode", orgUnit.getUAICode());
144            XMLUtils.createElement(contentHandler, "orgunit", attrs, orgUnit.getTitle());
145        }
146        
147        // Degree
148        OdfReferenceTableEntry degree = Optional.ofNullable(parentContext)
149            .map(pc -> pc.get("degree"))
150            .map(Object::toString)
151            .filter(StringUtils::isNotEmpty)
152            .map(_odfTableRefHelper::getItem)
153            .orElse(null);
154        if (degree != null)
155        {
156            attrs.clear();
157            attrs.addCDATAAttribute("id", degree.getId());
158            attrs.addCDATAAttribute("code", degree.getCode());
159            attrs.addCDATAAttribute("order", String.valueOf(degree.getOrder()));
160            XMLUtils.createElement(contentHandler, "degree", attrs, degree.getLabel(lang));
161        }
162        
163        Map<String, ContentAttributeDefinition> tableRefAttributeDefs = _odfTableRefHelper.getTableRefAttributeDefinitions(ProgramFactory.PROGRAM_CONTENT_TYPE);
164        
165        _saxPrograms(_getPrograms(catalog.getName(), lang, orgUnit, degree), tableRefAttributeDefs);
166        
167        // SAX entries of table references
168        XMLUtils.startElement(contentHandler, "enumerated-metadata");
169        for (ContentAttributeDefinition attributeDef : tableRefAttributeDefs.values())
170        {
171            _odfTableRefHelper.saxItems(contentHandler, attributeDef, lang);
172        }
173        XMLUtils.endElement(contentHandler, "enumerated-metadata");
174        
175        XMLUtils.endElement(contentHandler, "programs");
176        contentHandler.endDocument();
177    }
178    
179    /**
180     * Get programs from catalog, lang, orgunit and degree. Orgunit and degree can be null.
181     * @param catalog The catalog
182     * @param lang The content language
183     * @param orgUnit The root orgunit to display
184     * @param degree The degree
185     * @return An iterable of programs corresponding to the query with catalog, lang, orgunit and degree.
186     */
187    protected AmetysObjectIterable<Program> _getPrograms(String catalog, String lang, OrgUnit orgUnit, OdfReferenceTableEntry degree)
188    {
189        List<Expression> exprs = new ArrayList<>();
190        exprs.add(new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE));
191        exprs.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog));
192        exprs.add(new LanguageExpression(Operator.EQ, lang));
193
194        if (degree != null)
195        {
196            exprs.add(new StringExpression(AbstractProgram.DEGREE, Operator.EQ, degree.getId()));
197        }
198        
199        Expression[] orgUnitExpressions = Optional.ofNullable(orgUnit)
200            .map(_odfHelper::getSubOrgUnitIds)
201            .map(List::stream)
202            .orElseGet(Stream::empty)
203            .map(orgunitId -> new StringExpression(AbstractProgram.ORG_UNITS_REFERENCES, Operator.EQ, orgunitId))
204            .toArray(Expression[]::new);
205        if (orgUnitExpressions.length > 0)
206        {
207            exprs.add(new OrExpression(orgUnitExpressions));
208        }
209        
210        Expression programsExpression = new AndExpression(exprs.toArray(Expression[]::new));
211        
212        SortCriteria sortCriteria = new SortCriteria();
213        sortCriteria.addCriterion(Content.ATTRIBUTE_TITLE, true, true);
214  
215        String programsQuery = QueryHelper.getXPathQuery(null, ProgramFactory.PROGRAM_NODETYPE, programsExpression, sortCriteria);
216        AmetysObjectIterable<Program> programs = _resolver.query(programsQuery);
217        
218        return programs;
219    }
220    
221    /**
222     * SAX programs
223     * @param programs The programs to sax
224     * @param tableRefAttributeDefs The table reference attribute definitions
225     * @throws MalformedURLException if an error occurred
226     * @throws IOException if an error occurred
227     * @throws SAXException if an error occurred
228     */
229    protected void _saxPrograms(AmetysObjectIterable<Program> programs, Map<String, ContentAttributeDefinition> tableRefAttributeDefs) throws MalformedURLException, IOException, SAXException
230    {
231        for (Program program : programs)
232        {
233            try
234            {
235                _odfHelper.switchToLiveVersionIfNeeded(program);
236                AttributesImpl attrs = new AttributesImpl();
237                attrs.addCDATAAttribute("id", program.getId());
238                attrs.addCDATAAttribute("name", program.getName());
239                attrs.addCDATAAttribute("title", program.getTitle());
240                
241                XMLUtils.startElement(contentHandler, "program", attrs);
242                
243                _saxTableRefAttributeValues(program, tableRefAttributeDefs);
244                
245                XMLUtils.startElement(contentHandler, "fo");
246                SitemapSource src = null;      
247                
248                try
249                {
250                    String uri = "cocoon://_plugins/odf/_content/" + program.getName() + ".fo";
251                    src = (SitemapSource) resolver.resolveURI(uri);
252                    src.toSAX(new IgnoreRootHandler(contentHandler));
253                }
254                catch (UnknownAmetysObjectException e)
255                {
256                    // The content may be archived
257                }
258                finally
259                {
260                    resolver.release(src);
261                }
262                
263                XMLUtils.endElement(contentHandler, "fo");
264                XMLUtils.endElement(contentHandler, "program");
265            }
266            catch (NoLiveVersionException e)
267            {
268                getLogger().info("No live version found for program " + program.getTitle() + " (" + program.getCode() + "). The program will not appear in the PDF export.", e);
269            }
270        }
271    }
272    
273    /**
274     * SAX enumerated values of an attribute 
275     * @param program The program
276     * @param tableRefAttributeDefs The table reference attribute definitions
277     * @throws AmetysRepositoryException if an error occurred
278     * @throws SAXException if an error occurred
279     * @throws IOException if an error occurred
280     */
281    protected void _saxTableRefAttributeValues(Program program, Map<String, ContentAttributeDefinition> tableRefAttributeDefs) throws AmetysRepositoryException, SAXException, IOException
282    {
283        // Build a view containing all the reference tables attributes
284        View view = View.of(program.getModel(), tableRefAttributeDefs.keySet().toArray(new String[tableRefAttributeDefs.size()]));
285        
286        // Generate SAX events for the built view
287        XMLUtils.startElement(contentHandler, "metadata");
288        program.dataToSAX(contentHandler, view);
289        XMLUtils.endElement(contentHandler, "metadata");
290    }
291}