001/* 002 * Copyright 2010 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.plugins.repository.metadata; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.logger.AbstractLogEnabled; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.cocoon.xml.AttributesImpl; 037import org.apache.cocoon.xml.XMLUtils; 038import org.apache.commons.io.IOUtils; 039import org.apache.commons.lang.StringUtils; 040import org.apache.commons.lang3.LocaleUtils; 041import org.xml.sax.ContentHandler; 042import org.xml.sax.SAXException; 043 044import org.ametys.core.user.User; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.core.user.UserManager; 047import org.ametys.core.util.JSONUtils; 048import org.ametys.plugins.repository.AmetysObject; 049import org.ametys.plugins.repository.AmetysObjectIterable; 050import org.ametys.plugins.repository.AmetysObjectResolver; 051import org.ametys.plugins.repository.AmetysRepositoryException; 052import org.ametys.plugins.repository.TraversableAmetysObject; 053import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType; 054import org.ametys.runtime.i18n.I18nizableText; 055import org.ametys.runtime.parameter.Enumerator; 056import org.ametys.runtime.parameter.ParameterHelper; 057 058/** 059 * Component for helping SAXing metadata. 060 */ 061public class MetadataSaxer extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 062{ 063 /** The Avalon Role. */ 064 public static final String ROLE = MetadataSaxer.class.getName(); 065 066 /** The JSON conversion utilities. */ 067 protected JSONUtils _jsonUtils; 068 /** Ametys object resolver */ 069 protected AmetysObjectResolver _resolver; 070 /** The avalon context. */ 071 protected Context _context; 072 /** The user manager */ 073 protected UserManager _userManager; 074 075 @Override 076 public void contextualize(Context context) throws ContextException 077 { 078 _context = context; 079 } 080 081 @Override 082 public void service(ServiceManager manager) throws ServiceException 083 { 084 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 085 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 086 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 087 } 088 089 /** 090 * SAXes a composite metadata. 091 * @param contentHandler the content handler where to SAX into. 092 * @param compositeMetadata the composite metadata. 093 * @throws AmetysRepositoryException if an error occurs. 094 * @throws SAXException if an error occurs. 095 * @throws IOException if an error occurs. 096 */ 097 public void saxMetadata(ContentHandler contentHandler, CompositeMetadata compositeMetadata) throws AmetysRepositoryException, SAXException, IOException 098 { 099 _saxAllMetadata(contentHandler, compositeMetadata, "", null); 100 } 101 102 /** 103 * SAXes a composite metadata. 104 * @param contentHandler the content handler where to SAX into. 105 * @param compositeMetadata the composite metadata. 106 * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales. 107 * @throws AmetysRepositoryException if an error occurs. 108 * @throws SAXException if an error occurs. 109 * @throws IOException if an error occurs. 110 */ 111 public void saxMetadata(ContentHandler contentHandler, CompositeMetadata compositeMetadata, Locale locale) throws AmetysRepositoryException, SAXException, IOException 112 { 113 _saxAllMetadata(contentHandler, compositeMetadata, "", locale); 114 } 115 116 /** 117 * SAX all the metadata of a given composite metadata, with the given prefix. 118 * @param contentHandler the content handler where to SAX into. 119 * @param metadata the composite metadata. 120 * @param prefix the metadata path prefix. 121 * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales. 122 * @throws AmetysRepositoryException if an error occurs. 123 * @throws SAXException if an error occurs. 124 * @throws IOException if an error occurs. 125 */ 126 protected void _saxAllMetadata(ContentHandler contentHandler, CompositeMetadata metadata, String prefix, Locale locale) throws AmetysRepositoryException, SAXException, IOException 127 { 128 for (String metadataName : metadata.getMetadataNames()) 129 { 130 if (metadata.hasMetadata(metadataName)) 131 { 132 _saxMetadata(contentHandler, metadata, metadataName, prefix, locale); 133 } 134 } 135 } 136 137 /** 138 * SAX a single metadata. 139 * @param contentHandler the content handler where to SAX into. 140 * @param parentMetadata the parent composite metadata. 141 * @param metadataName the metadata name. 142 * @param prefix the metadata path prefix. 143 * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales. 144 * @throws AmetysRepositoryException if an error occurs. 145 * @throws SAXException if an error occurs. 146 * @throws IOException if an error occurs. 147 */ 148 protected void _saxMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix, Locale locale) throws AmetysRepositoryException, SAXException, IOException 149 { 150 if (parentMetadata.hasMetadata(metadataName)) 151 { 152 MetadataType metadataType = parentMetadata.getType(metadataName); 153 154 switch (metadataType) 155 { 156 case COMPOSITE: 157 CompositeMetadata subMetadata = parentMetadata.getCompositeMetadata(metadataName); 158 159 XMLUtils.startElement(contentHandler, metadataName); 160 161 // SAX all metadata contains in the current CompositeMetadata. 162 _saxAllMetadata(contentHandler, subMetadata, prefix + metadataName + "/", locale); 163 164 XMLUtils.endElement(contentHandler, metadataName); 165 break; 166 case USER: 167 _saxUserMetadata(contentHandler, parentMetadata, metadataName); 168 break; 169 case BINARY: 170 _saxBinaryMetadata(contentHandler, parentMetadata, metadataName, prefix); 171 break; 172 173 case RICHTEXT: 174 _saxRichTextMetadata(contentHandler, parentMetadata, metadataName, prefix); 175 break; 176 177 case DATE: 178 _saxDateMetadata(contentHandler, parentMetadata, metadataName, prefix); 179 break; 180 181 case OBJECT_COLLECTION: 182 TraversableAmetysObject objectCollection = parentMetadata.getObjectCollection(metadataName); 183 184 XMLUtils.startElement(contentHandler, metadataName); 185 186 // SAX all sub-objects. 187 for (AmetysObject subObject : objectCollection.getChildren()) 188 { 189 _saxObject(contentHandler, subObject, prefix + metadataName + "/", false, locale); 190 } 191 192 XMLUtils.endElement(contentHandler, metadataName); 193 break; 194 195 case MULTILINGUAL_STRING: 196 _saxMultilingualStringMetadata(contentHandler, parentMetadata, metadataName, prefix, locale); 197 break; 198 default: 199 _saxStringMetadata(contentHandler, parentMetadata, metadataName, prefix); 200 break; 201 } 202 } 203 } 204 205 /** 206 * SAX a binary metadata. 207 * @param contentHandler the content handler where to SAX into. 208 * @param parentMetadata the parent composite metadata. 209 * @param metadataName the metadata name. 210 * @param prefix the metadata path prefix. 211 * @throws AmetysRepositoryException if an error occurs. 212 * @throws SAXException if an error occurs. 213 */ 214 protected void _saxBinaryMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException 215 { 216 BinaryMetadata value = parentMetadata.getBinaryMetadata(metadataName); 217 String filename = value.getFilename(); 218 AttributesImpl attrs = new AttributesImpl(); 219 attrs.addCDATAAttribute("type", "metadata"); 220 attrs.addCDATAAttribute("mime-type", value.getMimeType()); 221 attrs.addCDATAAttribute("path", prefix + metadataName); 222 223 if (filename != null) 224 { 225 attrs.addCDATAAttribute("filename", filename); 226 } 227 attrs.addCDATAAttribute("size", String.valueOf(value.getLength())); 228 attrs.addCDATAAttribute("lastModified", ParameterHelper.valueToString(value.getLastModified())); 229 230 XMLUtils.createElement(contentHandler, metadataName, attrs); 231 } 232 233 /** 234 * SAX a rich-text metadata. 235 * @param contentHandler the content handler where to SAX into. 236 * @param parentMetadata the parent composite metadata. 237 * @param metadataName the metadata name. 238 * @param prefix the metadata path prefix. 239 * @throws AmetysRepositoryException if an error occurs. 240 * @throws SAXException if an error occurs. 241 * @throws IOException if an error occurs. 242 */ 243 protected void _saxRichTextMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException, IOException 244 { 245 RichText richText = parentMetadata.getRichText(metadataName); 246 AttributesImpl attrs = new AttributesImpl(); 247 248 attrs.addCDATAAttribute("mime-type", richText.getMimeType()); 249 attrs.addCDATAAttribute("lastModified", ParameterHelper.valueToString(richText.getLastModified())); 250 251 XMLUtils.startElement(contentHandler, metadataName, attrs); 252 253 String encoding = richText.getEncoding(); 254 255 try (InputStream is = richText.getInputStream()) 256 { 257 XMLUtils.data(contentHandler, IOUtils.toString(is, encoding)); 258 } 259 260 XMLUtils.endElement(contentHandler, metadataName); 261 } 262 263 /** 264 * SAX a string metadata. 265 * @param contentHandler the content handler where to SAX into. 266 * @param parentMetadata the parent composite metadata. 267 * @param metadataName the metadata name. 268 * @param prefix the metadata path prefix. 269 * @throws AmetysRepositoryException if an error occurs. 270 * @throws SAXException if an error occurs. 271 */ 272 protected void _saxStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException 273 { 274 String[] values = parentMetadata.getStringArray(metadataName, new String[0]); 275 276 for (String value : values) 277 { 278 XMLUtils.createElement(contentHandler, metadataName, value); 279 } 280 } 281 282 /** 283 * SAX a multilingual string metadata. 284 * @param contentHandler the content handler where to SAX into. 285 * @param parentMetadata the parent composite metadata. 286 * @param metadataName the metadata name. 287 * @param prefix the metadata path prefix 288 * @param currentLocale The current locale. Can be null. 289 * @throws AmetysRepositoryException if an error occurs. 290 * @throws SAXException if an error occurs. 291 */ 292 protected void _saxMultilingualStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix, Locale currentLocale) throws AmetysRepositoryException, SAXException 293 { 294 if (parentMetadata.hasMetadata(metadataName)) 295 { 296 MultilingualString multilingualString = parentMetadata.getMultilingualString(metadataName); 297 298 if (currentLocale == null) 299 { 300 XMLUtils.startElement(contentHandler, metadataName); 301 for (Locale locale : multilingualString.getLocales()) 302 { 303 XMLUtils.createElement(contentHandler, locale.toString(), multilingualString.getValue(locale)); 304 } 305 XMLUtils.endElement(contentHandler, metadataName); 306 } 307 else if (multilingualString.hasLocale(currentLocale)) 308 { 309 AttributesImpl attr = new AttributesImpl(); 310 attr.addCDATAAttribute("lang", currentLocale.toString()); 311 XMLUtils.createElement(contentHandler, metadataName, attr, multilingualString.getValue(currentLocale)); 312 } 313 else 314 { 315 // Get list of locales to search through, switching to default en if not found 316 List<Locale> localeLookupList = LocaleUtils.localeLookupList(currentLocale, new Locale("en")); 317 for (Locale locale : localeLookupList) 318 { 319 if (multilingualString.hasLocale(locale)) 320 { 321 AttributesImpl attr = new AttributesImpl(); 322 attr.addCDATAAttribute("lang", locale.toString()); 323 XMLUtils.createElement(contentHandler, metadataName, attr, multilingualString.getValue(locale)); 324 return; 325 } 326 } 327 328 // No locale found, get the first stored locale 329 Set<Locale> allLocales = multilingualString.getLocales(); 330 if (!allLocales.isEmpty()) 331 { 332 Locale firstLocale = allLocales.iterator().next(); 333 AttributesImpl attr = new AttributesImpl(); 334 attr.addCDATAAttribute("lang", firstLocale.toString()); 335 XMLUtils.createElement(contentHandler, metadataName, attr, multilingualString.getValue(firstLocale)); 336 } 337 } 338 } 339 } 340 341 /** 342 * SAX a string metadata. 343 * @param contentHandler the content handler where to SAX into. 344 * @param parentMetadata the parent composite metadata. 345 * @param metadataName the metadata name. 346 * @param enumerator The enumerator for labelisable values 347 * @throws AmetysRepositoryException if an error occurs. 348 * @throws SAXException if an error occurs. 349 */ 350 protected void _saxEnumeratedStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, Enumerator enumerator) throws AmetysRepositoryException, SAXException 351 { 352 String[] values = parentMetadata.getStringArray(metadataName, new String[0]); 353 354 for (String value : values) 355 { 356 if (StringUtils.isEmpty(value)) 357 { 358 XMLUtils.createElement(contentHandler, metadataName); 359 } 360 else 361 { 362 AttributesImpl attrs = new AttributesImpl(); 363 attrs.addCDATAAttribute("value", value); 364 365 try 366 { 367 I18nizableText i18n = enumerator.getEntry(value); 368 if (i18n != null) 369 { 370 XMLUtils.startElement(contentHandler, metadataName, attrs); 371 i18n.toSAX(contentHandler); 372 XMLUtils.endElement(contentHandler, metadataName); 373 } 374 else 375 { 376 XMLUtils.createElement(contentHandler, metadataName, attrs, value); 377 } 378 } 379 catch (Exception e) 380 { 381 getLogger().warn("Saxing enumerated String metadata '" + metadataName + "' required a label for enumerated value", e); 382 XMLUtils.createElement(contentHandler, metadataName, attrs, value); 383 } 384 } 385 386 } 387 } 388 389 /** 390 * SAX a metadata as a Date. 391 * @param contentHandler the content handler where to SAX into. 392 * @param parentMetadata the parent composite metadata. 393 * @param metadataName the metadata name. 394 * @param prefix the metadata path prefix. 395 * @throws AmetysRepositoryException if an error occurs. 396 * @throws SAXException if an error occurs. 397 */ 398 protected void _saxDateMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException 399 { 400 Date[] values = parentMetadata.getDateArray(metadataName, new Date[0]); 401 402 for (Date value : values) 403 { 404 XMLUtils.createElement(contentHandler, metadataName, ParameterHelper.valueToString(value)); 405 } 406 } 407 408 /** 409 * SAX a metadata as a User. 410 * @param contentHandler the content handler where to SAX into. 411 * @param parentMetadata the parent composite metadata. 412 * @param metadataName the metadata name. 413 * @throws SAXException if an error occurs 414 */ 415 protected void _saxUserMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException 416 { 417 UserIdentity[] values = parentMetadata.getUserArray(metadataName); 418 419 for (UserIdentity userIdentity : values) 420 { 421 User user = _userManager.getUser(userIdentity); 422 if (user != null) 423 { 424 AttributesImpl attrs = new AttributesImpl(); 425 attrs.addCDATAAttribute("login", userIdentity.getLogin()); 426 attrs.addCDATAAttribute("populationId", userIdentity.getPopulationId()); 427 attrs.addCDATAAttribute("email", user.getEmail()); 428 XMLUtils.createElement(contentHandler, metadataName, attrs, user.getFullName()); 429 } 430 } 431 } 432 433 /** 434 * SAX a metadata as a User as JSON. 435 * @param contentHandler the content handler where to SAX into. 436 * @param parentMetadata the parent composite metadata. 437 * @param metadataName the metadata name. 438 * @throws SAXException if an error occurs 439 */ 440 protected void _saxSingleUserMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException 441 { 442 UserIdentity userIdentity = parentMetadata.getUser(metadataName); 443 444 Map<String, Object> values = _userAsJson(userIdentity); 445 AttributesImpl attrs = new AttributesImpl(); 446 attrs.addCDATAAttribute("json", "true"); 447 448 String jsonString = _jsonUtils.convertObjectToJson(values); 449 XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString); 450 } 451 452 /** 453 * SAX a metadata as a User as JSON. 454 * @param contentHandler the content handler where to SAX into. 455 * @param parentMetadata the parent composite metadata. 456 * @param metadataName the metadata name. 457 * @throws SAXException if an error occurs 458 */ 459 protected void _saxMultipleUserMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException 460 { 461 UserIdentity[] userIdentities = parentMetadata.getUserArray(metadataName); 462 463 List<Map<String, Object>> values = new ArrayList<>(); 464 for (UserIdentity userIdentity : userIdentities) 465 { 466 values.add(_userAsJson(userIdentity)); 467 } 468 469 AttributesImpl attrs = new AttributesImpl(); 470 attrs.addCDATAAttribute("json", "true"); 471 472 String jsonString = _jsonUtils.convertObjectToJson(values); 473 XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString); 474 } 475 476 /** 477 * SAX an Object, i.e. its ID and name, and optionally its metadata. 478 * @param contentHandler the content handler where to SAX into. 479 * @param object the {@link AmetysObject} to sax. 480 * @param prefix the metadata path prefix. 481 * @param isDeep If <code>true</code> and the AmetysObject is a {@link MetadataAwareAmetysObject}, its metadata will be saxed. 482 * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales. 483 * @throws AmetysRepositoryException if an error occurs. 484 * @throws SAXException if an error occurs. 485 * @throws IOException if an error occurs. 486 */ 487 protected void _saxObject(ContentHandler contentHandler, AmetysObject object, String prefix, boolean isDeep, Locale locale) throws AmetysRepositoryException, IOException, SAXException 488 { 489 AttributesImpl attrs = new AttributesImpl(); 490 attrs.addCDATAAttribute("id", object.getId()); 491 attrs.addCDATAAttribute("name", object.getName()); 492 493 XMLUtils.startElement(contentHandler, "object", attrs); 494 495 if (object instanceof MetadataAwareAmetysObject && isDeep) 496 { 497 CompositeMetadata metaHolder = ((MetadataAwareAmetysObject) object).getMetadataHolder(); 498 499 _saxAllMetadata(contentHandler, metaHolder, prefix, locale); 500 } 501 502 XMLUtils.endElement(contentHandler, "object"); 503 } 504 505 /** 506 * Get the id of child Ametys objects 507 * @param objectCollection The parent object collection 508 * @return The id of child Ametys objects 509 */ 510 protected List<String> _getRefAmetysObjectIds (TraversableAmetysObject objectCollection) 511 { 512 List<String> refAOs = new ArrayList<>(); 513 514 try (AmetysObjectIterable<AmetysObject> children = objectCollection.getChildren()) 515 { 516 for (AmetysObject refAO : children) 517 { 518 refAOs.add(refAO.getId()); 519 } 520 } 521 522 return refAOs; 523 } 524 525 /** 526 * Get the JSON representation of a User metadata 527 * @param userIdentity The user identity 528 * @return The user as JSON 529 */ 530 protected Map<String, Object> _userAsJson (UserIdentity userIdentity) 531 { 532 Map<String, Object> json = new LinkedHashMap<>(); 533 534 json.put("login", userIdentity.getLogin()); 535 json.put("populationId", userIdentity.getPopulationId()); 536 537 return json; 538 } 539 540 /** 541 * Get the JSON representation of a {@link BinaryMetadata} 542 * @param binaryMetadata The metadata 543 * @param prefix The prefix 544 * @param metadataName The metadata name 545 * @return The binary as JSON 546 */ 547 protected Map<String, Object> _binaryAsJson (BinaryMetadata binaryMetadata, String prefix, String metadataName) 548 { 549 Map<String, Object> json = new LinkedHashMap<>(); 550 551 String filename = binaryMetadata.getFilename(); 552 553 json.put("type", "metadata"); 554 json.put("mimeType", binaryMetadata.getMimeType()); 555 json.put("path", prefix + metadataName); 556 557 if (filename != null) 558 { 559 json.put("filename", filename); 560 } 561 json.put("size", String.valueOf(binaryMetadata.getLength())); 562 json.put("lastModified", ParameterHelper.valueToString(binaryMetadata.getLastModified())); 563 564 return json; 565 } 566}