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