001/* 002 * Copyright 2017 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; 017 018import java.io.IOException; 019import java.util.Collection; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.Map; 024import java.util.NoSuchElementException; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.generation.ServiceableGenerator; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang3.ArrayUtils; 035import org.apache.commons.lang3.StringUtils; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.contenttype.ContentTypesHelper; 039import org.ametys.cms.contenttype.MetadataManager; 040import org.ametys.cms.contenttype.MetadataSet; 041import org.ametys.cms.repository.Content; 042import org.ametys.core.util.DateUtils; 043import org.ametys.odf.ODFHelper; 044import org.ametys.odf.ProgramItem; 045import org.ametys.odf.course.Course; 046import org.ametys.odf.enumeration.OdfReferenceTableEntry; 047import org.ametys.odf.enumeration.OdfReferenceTableHelper; 048import org.ametys.odf.program.AbstractProgram; 049import org.ametys.odf.program.Program; 050import org.ametys.odf.tree.OdfClassificationHandler; 051import org.ametys.plugins.repository.AmetysObjectIterable; 052import org.ametys.plugins.repository.AmetysObjectIterator; 053import org.ametys.plugins.repository.AmetysObjectResolver; 054import org.ametys.plugins.repository.AmetysRepositoryException; 055import org.ametys.plugins.repository.UnknownAmetysObjectException; 056import org.ametys.plugins.repository.version.VersionAwareAmetysObject; 057 058/** 059 * Generate the ODF structure with 2 levels (metadata), the catalog and the lang. It's possible to determine a metadataset to sax data. 060 * 061 * You should call this generator with the following parameters : 062 * - catalog : identifier of the catalog 063 * - lang : language code (fr, en, etc.) 064 * - level1 : name of the metadata for the first level 065 * - level2 : name of the metadata for the second level 066 * - metadataSet (optional) : name of the metadata set to sax values 067 * 068 */ 069public class ExportCatalogByLevelsGenerator extends ServiceableGenerator 070{ 071 /** The AmetysObject resolver */ 072 protected AmetysObjectResolver _resolver; 073 074 /** The Metadata Manager */ 075 protected MetadataManager _metadataManager; 076 077 /** The content type helper */ 078 protected ContentTypesHelper _contentTypesHelper; 079 080 /** The ODF helper */ 081 protected ODFHelper _odfHelper; 082 083 /** The ODF classification handler */ 084 protected OdfClassificationHandler _odfClassificationHandler; 085 086 /** The helper for reference tables*/ 087 protected OdfReferenceTableHelper _odfRefTableHelper; 088 089 @Override 090 public void service(ServiceManager smanager) throws ServiceException 091 { 092 super.service(smanager); 093 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 094 _metadataManager = (MetadataManager) smanager.lookup(MetadataManager.ROLE); 095 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 096 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 097 _odfClassificationHandler = (OdfClassificationHandler) smanager.lookup(OdfClassificationHandler.ROLE); 098 _odfRefTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE); 099 } 100 101 @Override 102 public void generate() throws IOException, SAXException, ProcessingException 103 { 104 contentHandler.startDocument(); 105 XMLUtils.startElement(contentHandler, "Ametys-ODF"); 106 107 /* Test parameters */ 108 Map<String, String> wsParameters = new HashMap<>(); 109 if (getParameters(wsParameters)) 110 { 111 /* Build and SAX ODF Structure */ 112 AmetysObjectIterable<Program> programs = getConcernedPrograms(wsParameters); 113 114 // Switch to version label if not empty 115 String versionLabel = parameters.getParameter("versionLabel", null); 116 if (StringUtils.isNotBlank(versionLabel)) 117 { 118 // wrap the current iterable into a iteratable which will filtered programs by version label 119 programs = new FilteredByVersionLabelIterable<>(programs, versionLabel); 120 } 121 122 Map<String, Map<String, Collection<Program>>> odfStructure = _odfClassificationHandler.organizeProgramsByLevels(programs, wsParameters.get("level1"), wsParameters.get("level2")); 123 124 String level1 = wsParameters.get("level1"); 125 String level2 = wsParameters.get("level2"); 126 String metadataSet = wsParameters.get("metadataSet"); 127 128 for (String level1Value : odfStructure.keySet()) 129 { 130 AttributesImpl attrs = new AttributesImpl(); 131 _addLevelAttributes(attrs, level1Value, wsParameters.get("lang")); 132 XMLUtils.startElement(contentHandler, level1, attrs); 133 134 Map<String, Collection<Program>> level2Values = odfStructure.get(level1Value); 135 for (String level2Value : level2Values.keySet()) 136 { 137 attrs.clear(); 138 _addLevelAttributes(attrs, level2Value, wsParameters.get("lang")); 139 140 XMLUtils.startElement(contentHandler, level2, attrs); 141 for (Program program : level2Values.get(level2Value)) 142 { 143 _saxStructure(program, program, metadataSet, wsParameters); 144 } 145 XMLUtils.endElement(contentHandler, level2); 146 } 147 148 XMLUtils.endElement(contentHandler, level1); 149 } 150 } 151 152 XMLUtils.endElement(contentHandler, "Ametys-ODF"); 153 contentHandler.endDocument(); 154 } 155 156 /** 157 * Add attributes for classification level 158 * @param attrs The XML attributes 159 * @param value The level's value 160 * @param lang The language 161 */ 162 protected void _addLevelAttributes(AttributesImpl attrs, String value, String lang) 163 { 164 if (_resolver.hasAmetysObjectForId(value)) 165 { 166 Content content = _resolver.resolveById(value); 167 168 if (_odfRefTableHelper.isTableReferenceEntry(content)) 169 { 170 OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content); 171 attrs.addCDATAAttribute("id", entry.getId()); 172 attrs.addCDATAAttribute("code", entry.getCode()); 173 attrs.addCDATAAttribute("title", entry.getLabel(lang)); 174 } 175 else 176 { 177 attrs.addCDATAAttribute("id", content.getId()); 178 attrs.addCDATAAttribute("title", content.getTitle()); 179 } 180 } 181 else 182 { 183 attrs.addCDATAAttribute("value", value); 184 } 185 } 186 187 /** 188 * Get the parameters from the request and test it. 189 * @param wsParameters Map of parameters to fill 190 * @return false if a parameter is missing or something going wrong with the parameters, otherwise true 191 * @throws SAXException if an error occured 192 */ 193 protected boolean getParameters(Map<String, String> wsParameters) throws SAXException 194 { 195 Request request = ObjectModelHelper.getRequest(objectModel); 196 197 String catalog = getParameter(request, "catalog"); 198 String lang = getParameter(request, "lang"); 199 String level1 = getParameter(request, "level1"); 200 String level2 = getParameter(request, "level2"); 201 202 boolean isValidLevelParameters = true; 203 if (level1 != null && !_odfClassificationHandler.isEligibleMetadataForLevel(level1, true)) 204 { 205 XMLUtils.createElement(contentHandler, "error", "The metadata " + level1 + " is not an eligible metadata for the export"); 206 isValidLevelParameters = false; 207 } 208 209 if (level2 != null && !_odfClassificationHandler.isEligibleMetadataForLevel(level2, true)) 210 { 211 XMLUtils.createElement(contentHandler, "error", "The metadata " + level2 + " is not an eligible metadata for the export"); 212 isValidLevelParameters = false; 213 } 214 215 wsParameters.put("catalog", catalog); 216 wsParameters.put("lang", lang); 217 wsParameters.put("level1", level1); 218 wsParameters.put("level2", level2); 219 220 String metadataSet = request.getParameter("metadataSet"); 221 if (StringUtils.isBlank(metadataSet)) 222 { 223 metadataSet = "main"; 224 } 225 wsParameters.put("metadataSet", metadataSet); 226 227 return catalog != null && lang != null && isValidLevelParameters && level1 != null && level2 != null; 228 } 229 230 /** 231 * Get the parameter from the request and test if it's not null or blank. 232 * Sax an error if the parameter is missing or empty. 233 * @param request The request 234 * @param parameterName The parameter name 235 * @return null when the parameter is missing or empty, otherwise the parameter value 236 * @throws SAXException if an error occured 237 */ 238 protected String getParameter(Request request, String parameterName) throws SAXException 239 { 240 String parameterValue = parameters.getParameter(parameterName, request.getParameter(parameterName)); 241 242 if (StringUtils.isBlank(parameterValue)) 243 { 244 XMLUtils.createElement(contentHandler, "error", "Missing parameter (cannot be empty) : " + parameterName); 245 parameterValue = null; 246 } 247 248 return parameterValue; 249 } 250 251 /** 252 * Get the programs to SAX. 253 * @param wsParameters Parameters of the web service 254 * @return A Collection of programs 255 */ 256 protected AmetysObjectIterable<Program> getConcernedPrograms(Map<String, String> wsParameters) 257 { 258 return _odfClassificationHandler.getPrograms(wsParameters.get("catalog"), wsParameters.get("lang"), wsParameters.get("level1"), null, wsParameters.get("level2"), null, null, null, null); 259 } 260 261 /** 262 * Sax the structure of the parentProgram by exploring its children and saxing metadata containing into the passed metadataSet. 263 * @param parentProgram Initial program 264 * @param programItem Part of the program to explore 265 * @param metadataSetName Name of the metadata set to SAX 266 * @param wsParameters Parameters of the web service 267 * @throws AmetysRepositoryException if an error occured 268 * @throws SAXException if an error occured 269 * @throws IOException if an error occured 270 */ 271 private void _saxStructure(Program parentProgram, ProgramItem programItem, String metadataSetName, Map<String, String> wsParameters) throws AmetysRepositoryException, SAXException, IOException 272 { 273 if (programItem instanceof AbstractProgram || programItem instanceof Course) 274 { 275 Content content = (Content) programItem; 276 277 String contentType = content.getTypes()[0]; 278 contentType = contentType.substring(contentType.lastIndexOf(".") + 1); 279 280 AttributesImpl attrs = getContentAttributes(programItem, parentProgram, wsParameters); 281 282 XMLUtils.startElement(contentHandler, contentType, attrs); 283 284 /* SAX metadata for flat level */ 285 MetadataSet metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); 286 _metadataManager.saxMetadata(contentHandler, content, metadataSet, null); 287 288 /* SAX structure of the ProgramItem */ 289 _saxChildren(parentProgram, programItem, metadataSetName, wsParameters); 290 291 XMLUtils.endElement(contentHandler, contentType); 292 } 293 else 294 { 295 /* SAX structure of the ProgramItem */ 296 _saxChildren(parentProgram, programItem, metadataSetName, wsParameters); 297 } 298 } 299 300 /** 301 * Explore and sax children of the passed program item. 302 * @param parentProgram Initial program 303 * @param programItem Part of the program to explore 304 * @param metadataSetName Name of the metadata set to SAX 305 * @param wsParameters Parameters of the web service 306 * @throws AmetysRepositoryException if an error occured 307 * @throws SAXException if an error occured 308 * @throws IOException if an error occured 309 */ 310 private void _saxChildren(Program parentProgram, ProgramItem programItem, String metadataSetName, Map<String, String> wsParameters) throws AmetysRepositoryException, SAXException, IOException 311 { 312 for (ProgramItem child : _odfHelper.getChildProgramItems(programItem)) 313 { 314 _saxStructure(parentProgram, child, metadataSetName, wsParameters); 315 } 316 } 317 318 /** 319 * Get attributes for the current saxed content (title, id, etc.). 320 * @param programItem Part of the program to get attributes 321 * @param parentProgram Initial program 322 * @param wsParameters Parameters of the web service 323 * @return The attributes to sax 324 */ 325 protected AttributesImpl getContentAttributes(ProgramItem programItem, Program parentProgram, Map<String, String> wsParameters) 326 { 327 Content content = (Content) programItem; 328 329 AttributesImpl attrs = new AttributesImpl(); 330 attrs.addCDATAAttribute("title", content.getTitle()); 331 attrs.addCDATAAttribute("id", content.getId()); 332 Date lastValidated = content.getLastValidationDate(); 333 if (lastValidated != null) 334 { 335 attrs.addCDATAAttribute("lastValidated", DateUtils.dateToString(lastValidated)); 336 } 337 return attrs; 338 } 339 340 class FilteredByVersionLabelIterable<P extends VersionAwareAmetysObject> implements AmetysObjectIterable<P> 341 { 342 private AmetysObjectIterable<P> _initialIterable; 343 private String _versionLabel; 344 345 /** 346 * Creates a {@link AmetysObjectIterable} which will filter and get elements with given version label 347 * @param it the initial {@link AmetysObjectIterable}s 348 * @param versionLabel The version label to filter by 349 */ 350 public FilteredByVersionLabelIterable(AmetysObjectIterable<P> it, String versionLabel) 351 { 352 _initialIterable = it; 353 _versionLabel = versionLabel; 354 } 355 356 public long getSize() 357 { 358 return -1; 359 } 360 361 public AmetysObjectIterator<P> iterator() 362 { 363 return new FilteredByVersionLabelIterator(_initialIterable.iterator(), _initialIterable.getSize(), _versionLabel); 364 } 365 366 public void close() 367 { 368 // nothing to do 369 } 370 371 class FilteredByVersionLabelIterator implements AmetysObjectIterator<P> 372 { 373 private long _invalids; 374 private Iterator<P> _it; 375 private int _pos; 376 private long _size; 377 private P _nextObject; 378 private String _label; 379 380 public FilteredByVersionLabelIterator(Iterator<P> it, long size, String label) 381 { 382 _it = it; 383 _size = size; 384 _invalids = 0; 385 _label = label; 386 } 387 388 public boolean hasNext() 389 { 390 // Prefetch the next object 391 if (_nextObject == null) 392 { 393 while (_it.hasNext()) 394 { 395 P next = _it.next(); 396 try 397 { 398 String[] allLabels = next.getAllLabels(); 399 if (ArrayUtils.contains(allLabels, _label)) 400 { 401 next.switchToLabel(_label); 402 _nextObject = next; 403 return true; 404 } 405 else 406 { 407 // Go to next element 408 _invalids++; 409 } 410 } 411 catch (UnknownAmetysObjectException e) 412 { 413 // Go to next element 414 _invalids++; 415 } 416 } 417 return false; 418 } 419 return true; 420 } 421 422 public P next() 423 { 424 if (!hasNext()) 425 { 426 throw new NoSuchElementException(); 427 } 428 429 try 430 { 431 _pos++; 432 return _nextObject; 433 } 434 finally 435 { 436 _nextObject = null; 437 } 438 } 439 440 public long getSize() 441 { 442 return _size - _invalids; 443 } 444 445 public long getPosition() 446 { 447 return _pos; 448 } 449 } 450 } 451}