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