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