1 // ================================================================================================= 2 // ADOBE SYSTEMS INCORPORATED 3 // Copyright 2006 Adobe Systems Incorporated 4 // All Rights Reserved 5 // 6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms 7 // of the Adobe license agreement accompanying it. 8 // ================================================================================================= 9 10 package com.adobe.xmp.impl; 11 12 import java.util.ArrayList; 13 import java.util.Collections; 14 import java.util.HashMap; 15 import java.util.Iterator; 16 import java.util.List; 17 import java.util.Map; 18 import java.util.TreeMap; 19 import java.util.regex.Pattern; 20 21 import com.adobe.xmp.XMPConst; 22 import com.adobe.xmp.XMPError; 23 import com.adobe.xmp.XMPException; 24 import com.adobe.xmp.XMPSchemaRegistry; 25 import com.adobe.xmp.options.AliasOptions; 26 import com.adobe.xmp.properties.XMPAliasInfo; 27 28 29 /** 30 * The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There 31 * is only one single instance used by the toolkit. 32 * 33 * @since 27.01.2006 34 */ 35 public final class XMPSchemaRegistryImpl implements XMPSchemaRegistry, XMPConst 36 { 37 /** a map from a namespace URI to its registered prefix */ 38 private Map namespaceToPrefixMap = new HashMap(); 39 40 /** a map from a prefix to the associated namespace URI */ 41 private Map prefixToNamespaceMap = new HashMap(); 42 43 /** a map of all registered aliases. 44 * The map is a relationship from a qname to an <code>XMPAliasInfo</code>-object. */ 45 private Map aliasMap = new HashMap(); 46 /** The pattern that must not be contained in simple properties */ 47 private Pattern p = Pattern.compile("[/*?\\[\\]]"); 48 49 50 /** 51 * Performs the initialisation of the registry with the default namespaces, aliases and global 52 * options. 53 */ XMPSchemaRegistryImpl()54 public XMPSchemaRegistryImpl() 55 { 56 try 57 { 58 registerStandardNamespaces(); 59 registerStandardAliases(); 60 } 61 catch (XMPException e) 62 { 63 throw new RuntimeException("The XMPSchemaRegistry cannot be initialized!"); 64 } 65 } 66 67 68 // --------------------------------------------------------------------------------------------- 69 // Namespace Functions 70 71 72 /** 73 * @see XMPSchemaRegistry#registerNamespace(String, String) 74 */ registerNamespace(String namespaceURI, String suggestedPrefix)75 public synchronized String registerNamespace(String namespaceURI, String suggestedPrefix) 76 throws XMPException 77 { 78 ParameterAsserts.assertSchemaNS(namespaceURI); 79 ParameterAsserts.assertPrefix(suggestedPrefix); 80 81 if (suggestedPrefix.charAt(suggestedPrefix.length() - 1) != ':') 82 { 83 suggestedPrefix += ':'; 84 } 85 86 if (!Utils.isXMLNameNS(suggestedPrefix.substring(0, 87 suggestedPrefix.length() - 1))) 88 { 89 throw new XMPException("The prefix is a bad XML name", XMPError.BADXML); 90 } 91 92 String registeredPrefix = (String) namespaceToPrefixMap.get(namespaceURI); 93 String registeredNS = (String) prefixToNamespaceMap.get(suggestedPrefix); 94 if (registeredPrefix != null) 95 { 96 // Return the actual prefix 97 return registeredPrefix; 98 } 99 else 100 { 101 if (registeredNS != null) 102 { 103 // the namespace is new, but the prefix is already engaged, 104 // we generate a new prefix out of the suggested 105 String generatedPrefix = suggestedPrefix; 106 for (int i = 1; prefixToNamespaceMap.containsKey(generatedPrefix); i++) 107 { 108 generatedPrefix = suggestedPrefix 109 .substring(0, suggestedPrefix.length() - 1) 110 + "_" + i + "_:"; 111 } 112 suggestedPrefix = generatedPrefix; 113 } 114 prefixToNamespaceMap.put(suggestedPrefix, namespaceURI); 115 namespaceToPrefixMap.put(namespaceURI, suggestedPrefix); 116 117 // Return the suggested prefix 118 return suggestedPrefix; 119 } 120 } 121 122 123 /** 124 * @see XMPSchemaRegistry#deleteNamespace(String) 125 */ deleteNamespace(String namespaceURI)126 public synchronized void deleteNamespace(String namespaceURI) 127 { 128 String prefixToDelete = getNamespacePrefix(namespaceURI); 129 if (prefixToDelete != null) 130 { 131 namespaceToPrefixMap.remove(namespaceURI); 132 prefixToNamespaceMap.remove(prefixToDelete); 133 } 134 } 135 136 137 /** 138 * @see XMPSchemaRegistry#getNamespacePrefix(String) 139 */ getNamespacePrefix(String namespaceURI)140 public synchronized String getNamespacePrefix(String namespaceURI) 141 { 142 return (String) namespaceToPrefixMap.get(namespaceURI); 143 } 144 145 146 /** 147 * @see XMPSchemaRegistry#getNamespaceURI(String) 148 */ getNamespaceURI(String namespacePrefix)149 public synchronized String getNamespaceURI(String namespacePrefix) 150 { 151 if (namespacePrefix != null && !namespacePrefix.endsWith(":")) 152 { 153 namespacePrefix += ":"; 154 } 155 return (String) prefixToNamespaceMap.get(namespacePrefix); 156 } 157 158 159 /** 160 * @see XMPSchemaRegistry#getNamespaces() 161 */ getNamespaces()162 public synchronized Map getNamespaces() 163 { 164 return Collections.unmodifiableMap(new TreeMap(namespaceToPrefixMap)); 165 } 166 167 168 /** 169 * @see XMPSchemaRegistry#getPrefixes() 170 */ getPrefixes()171 public synchronized Map getPrefixes() 172 { 173 return Collections.unmodifiableMap(new TreeMap(prefixToNamespaceMap)); 174 } 175 176 177 /** 178 * Register the standard namespaces of schemas and types that are included in the XMP 179 * Specification and some other Adobe private namespaces. 180 * Note: This method is not lock because only called by the constructor. 181 * 182 * @throws XMPException Forwards processing exceptions 183 */ registerStandardNamespaces()184 private void registerStandardNamespaces() throws XMPException 185 { 186 // register standard namespaces 187 registerNamespace(NS_XML, "xml"); 188 registerNamespace(NS_RDF, "rdf"); 189 registerNamespace(NS_DC, "dc"); 190 registerNamespace(NS_IPTCCORE, "Iptc4xmpCore"); 191 192 // register Adobe standard namespaces 193 registerNamespace(NS_X, "x"); 194 registerNamespace(NS_IX, "iX"); 195 196 registerNamespace(NS_XMP, "xmp"); 197 registerNamespace(NS_XMP_RIGHTS, "xmpRights"); 198 registerNamespace(NS_XMP_MM, "xmpMM"); 199 registerNamespace(NS_XMP_BJ, "xmpBJ"); 200 registerNamespace(NS_XMP_NOTE, "xmpNote"); 201 202 registerNamespace(NS_PDF, "pdf"); 203 registerNamespace(NS_PDFX, "pdfx"); 204 registerNamespace(NS_PDFX_ID, "pdfxid"); 205 registerNamespace(NS_PDFA_SCHEMA, "pdfaSchema"); 206 registerNamespace(NS_PDFA_PROPERTY, "pdfaProperty"); 207 registerNamespace(NS_PDFA_TYPE, "pdfaType"); 208 registerNamespace(NS_PDFA_FIELD, "pdfaField"); 209 registerNamespace(NS_PDFA_ID, "pdfaid"); 210 registerNamespace(NS_PDFA_EXTENSION, "pdfaExtension"); 211 registerNamespace(NS_PHOTOSHOP, "photoshop"); 212 registerNamespace(NS_PSALBUM, "album"); 213 registerNamespace(NS_EXIF, "exif"); 214 registerNamespace(NS_EXIF_AUX, "aux"); 215 registerNamespace(NS_TIFF, "tiff"); 216 registerNamespace(NS_PNG, "png"); 217 registerNamespace(NS_JPEG, "jpeg"); 218 registerNamespace(NS_JP2K, "jp2k"); 219 registerNamespace(NS_CAMERARAW, "crs"); 220 registerNamespace(NS_ADOBESTOCKPHOTO, "bmsp"); 221 registerNamespace(NS_CREATOR_ATOM, "creatorAtom"); 222 registerNamespace(NS_ASF, "asf"); 223 registerNamespace(NS_WAV, "wav"); 224 225 // register Adobe private namespaces 226 registerNamespace(NS_DM, "xmpDM"); 227 registerNamespace(NS_TRANSIENT, "xmpx"); 228 229 // register Adobe standard type namespaces 230 registerNamespace(TYPE_TEXT, "xmpT"); 231 registerNamespace(TYPE_PAGEDFILE, "xmpTPg"); 232 registerNamespace(TYPE_GRAPHICS, "xmpG"); 233 registerNamespace(TYPE_IMAGE, "xmpGImg"); 234 registerNamespace(TYPE_FONT, "stFNT"); 235 registerNamespace(TYPE_DIMENSIONS, "stDim"); 236 registerNamespace(TYPE_RESOURCEEVENT, "stEvt"); 237 registerNamespace(TYPE_RESOURCEREF, "stRef"); 238 registerNamespace(TYPE_ST_VERSION, "stVer"); 239 registerNamespace(TYPE_ST_JOB, "stJob"); 240 registerNamespace(TYPE_MANIFESTITEM, "stMfs"); 241 registerNamespace(TYPE_IDENTIFIERQUAL, "xmpidq"); 242 } 243 244 245 246 // --------------------------------------------------------------------------------------------- 247 // Alias Functions 248 249 250 /** 251 * @see XMPSchemaRegistry#resolveAlias(String, String) 252 */ resolveAlias(String aliasNS, String aliasProp)253 public synchronized XMPAliasInfo resolveAlias(String aliasNS, String aliasProp) 254 { 255 String aliasPrefix = getNamespacePrefix(aliasNS); 256 if (aliasPrefix == null) 257 { 258 return null; 259 } 260 261 return (XMPAliasInfo) aliasMap.get(aliasPrefix + aliasProp); 262 } 263 264 265 /** 266 * @see XMPSchemaRegistry#findAlias(java.lang.String) 267 */ findAlias(String qname)268 public synchronized XMPAliasInfo findAlias(String qname) 269 { 270 return (XMPAliasInfo) aliasMap.get(qname); 271 } 272 273 274 /** 275 * @see XMPSchemaRegistry#findAliases(String) 276 */ findAliases(String aliasNS)277 public synchronized XMPAliasInfo[] findAliases(String aliasNS) 278 { 279 String prefix = getNamespacePrefix(aliasNS); 280 List result = new ArrayList(); 281 if (prefix != null) 282 { 283 for (Iterator it = aliasMap.keySet().iterator(); it.hasNext();) 284 { 285 String qname = (String) it.next(); 286 if (qname.startsWith(prefix)) 287 { 288 result.add(findAlias(qname)); 289 } 290 } 291 292 } 293 return (XMPAliasInfo[]) result.toArray(new XMPAliasInfo[result.size()]); 294 } 295 296 297 /** 298 * Associates an alias name with an actual name. 299 * <p> 300 * Define a alias mapping from one namespace/property to another. Both 301 * property names must be simple names. An alias can be a direct mapping, 302 * where the alias and actual have the same data type. It is also possible 303 * to map a simple alias to an item in an array. This can either be to the 304 * first item in the array, or to the 'x-default' item in an alt-text array. 305 * Multiple alias names may map to the same actual, as long as the forms 306 * match. It is a no-op to reregister an alias in an identical fashion. 307 * Note: This method is not locking because only called by registerStandardAliases 308 * which is only called by the constructor. 309 * Note2: The method is only package-private so that it can be tested with unittests 310 * 311 * @param aliasNS 312 * The namespace URI for the alias. Must not be null or the empty 313 * string. 314 * @param aliasProp 315 * The name of the alias. Must be a simple name, not null or the 316 * empty string and not a general path expression. 317 * @param actualNS 318 * The namespace URI for the actual. Must not be null or the 319 * empty string. 320 * @param actualProp 321 * The name of the actual. Must be a simple name, not null or the 322 * empty string and not a general path expression. 323 * @param aliasForm 324 * Provides options for aliases for simple aliases to array 325 * items. This is needed to know what kind of array to create if 326 * set for the first time via the simple alias. Pass 327 * <code>XMP_NoOptions</code>, the default value, for all 328 * direct aliases regardless of whether the actual data type is 329 * an array or not (see {@link AliasOptions}). 330 * @throws XMPException 331 * for inconsistant aliases. 332 */ registerAlias(String aliasNS, String aliasProp, final String actualNS, final String actualProp, final AliasOptions aliasForm)333 synchronized void registerAlias(String aliasNS, String aliasProp, final String actualNS, 334 final String actualProp, final AliasOptions aliasForm) throws XMPException 335 { 336 ParameterAsserts.assertSchemaNS(aliasNS); 337 ParameterAsserts.assertPropName(aliasProp); 338 ParameterAsserts.assertSchemaNS(actualNS); 339 ParameterAsserts.assertPropName(actualProp); 340 341 // Fix the alias options 342 final AliasOptions aliasOpts = aliasForm != null ? 343 new AliasOptions(XMPNodeUtils.verifySetOptions( 344 aliasForm.toPropertyOptions(), null).getOptions()) : 345 new AliasOptions(); 346 347 if (p.matcher(aliasProp).find() || p.matcher(actualProp).find()) 348 { 349 throw new XMPException("Alias and actual property names must be simple", 350 XMPError.BADXPATH); 351 } 352 353 // check if both namespaces are registered 354 final String aliasPrefix = getNamespacePrefix(aliasNS); 355 final String actualPrefix = getNamespacePrefix(actualNS); 356 if (aliasPrefix == null) 357 { 358 throw new XMPException("Alias namespace is not registered", XMPError.BADSCHEMA); 359 } 360 else if (actualPrefix == null) 361 { 362 throw new XMPException("Actual namespace is not registered", 363 XMPError.BADSCHEMA); 364 } 365 366 String key = aliasPrefix + aliasProp; 367 368 // check if alias is already existing 369 if (aliasMap.containsKey(key)) 370 { 371 throw new XMPException("Alias is already existing", XMPError.BADPARAM); 372 } 373 else if (aliasMap.containsKey(actualPrefix + actualProp)) 374 { 375 throw new XMPException( 376 "Actual property is already an alias, use the base property", 377 XMPError.BADPARAM); 378 } 379 380 XMPAliasInfo aliasInfo = new XMPAliasInfo() 381 { 382 /** 383 * @see XMPAliasInfo#getNamespace() 384 */ 385 public String getNamespace() 386 { 387 return actualNS; 388 } 389 390 /** 391 * @see XMPAliasInfo#getPrefix() 392 */ 393 public String getPrefix() 394 { 395 return actualPrefix; 396 } 397 398 /** 399 * @see XMPAliasInfo#getPropName() 400 */ 401 public String getPropName() 402 { 403 return actualProp; 404 } 405 406 /** 407 * @see XMPAliasInfo#getAliasForm() 408 */ 409 public AliasOptions getAliasForm() 410 { 411 return aliasOpts; 412 } 413 414 public String toString() 415 { 416 return actualPrefix + actualProp + " NS(" + actualNS + "), FORM (" 417 + getAliasForm() + ")"; 418 } 419 }; 420 421 aliasMap.put(key, aliasInfo); 422 } 423 424 425 /** 426 * @see XMPSchemaRegistry#getAliases() 427 */ getAliases()428 public synchronized Map getAliases() 429 { 430 return Collections.unmodifiableMap(new TreeMap(aliasMap)); 431 } 432 433 434 /** 435 * Register the standard aliases. 436 * Note: This method is not lock because only called by the constructor. 437 * 438 * @throws XMPException If the registrations of at least one alias fails. 439 */ registerStandardAliases()440 private void registerStandardAliases() throws XMPException 441 { 442 AliasOptions aliasToArrayOrdered = new AliasOptions().setArrayOrdered(true); 443 AliasOptions aliasToArrayAltText = new AliasOptions().setArrayAltText(true); 444 445 446 // Aliases from XMP to DC. 447 registerAlias(NS_XMP, "Author", NS_DC, "creator", aliasToArrayOrdered); 448 registerAlias(NS_XMP, "Authors", NS_DC, "creator", null); 449 registerAlias(NS_XMP, "Description", NS_DC, "description", null); 450 registerAlias(NS_XMP, "Format", NS_DC, "format", null); 451 registerAlias(NS_XMP, "Keywords", NS_DC, "subject", null); 452 registerAlias(NS_XMP, "Locale", NS_DC, "language", null); 453 registerAlias(NS_XMP, "Title", NS_DC, "title", null); 454 registerAlias(NS_XMP_RIGHTS, "Copyright", NS_DC, "rights", null); 455 456 // Aliases from PDF to DC and XMP. 457 registerAlias(NS_PDF, "Author", NS_DC, "creator", aliasToArrayOrdered); 458 registerAlias(NS_PDF, "BaseURL", NS_XMP, "BaseURL", null); 459 registerAlias(NS_PDF, "CreationDate", NS_XMP, "CreateDate", null); 460 registerAlias(NS_PDF, "Creator", NS_XMP, "CreatorTool", null); 461 registerAlias(NS_PDF, "ModDate", NS_XMP, "ModifyDate", null); 462 registerAlias(NS_PDF, "Subject", NS_DC, "description", aliasToArrayAltText); 463 registerAlias(NS_PDF, "Title", NS_DC, "title", aliasToArrayAltText); 464 465 // Aliases from PHOTOSHOP to DC and XMP. 466 registerAlias(NS_PHOTOSHOP, "Author", NS_DC, "creator", aliasToArrayOrdered); 467 registerAlias(NS_PHOTOSHOP, "Caption", NS_DC, "description", aliasToArrayAltText); 468 registerAlias(NS_PHOTOSHOP, "Copyright", NS_DC, "rights", aliasToArrayAltText); 469 registerAlias(NS_PHOTOSHOP, "Keywords", NS_DC, "subject", null); 470 registerAlias(NS_PHOTOSHOP, "Marked", NS_XMP_RIGHTS, "Marked", null); 471 registerAlias(NS_PHOTOSHOP, "Title", NS_DC, "title", aliasToArrayAltText); 472 registerAlias(NS_PHOTOSHOP, "WebStatement", NS_XMP_RIGHTS, "WebStatement", null); 473 474 // Aliases from TIFF and EXIF to DC and XMP. 475 registerAlias(NS_TIFF, "Artist", NS_DC, "creator", aliasToArrayOrdered); 476 registerAlias(NS_TIFF, "Copyright", NS_DC, "rights", null); 477 registerAlias(NS_TIFF, "DateTime", NS_XMP, "ModifyDate", null); 478 registerAlias(NS_TIFF, "ImageDescription", NS_DC, "description", null); 479 registerAlias(NS_TIFF, "Software", NS_XMP, "CreatorTool", null); 480 481 // Aliases from PNG (Acrobat ImageCapture) to DC and XMP. 482 registerAlias(NS_PNG, "Author", NS_DC, "creator", aliasToArrayOrdered); 483 registerAlias(NS_PNG, "Copyright", NS_DC, "rights", aliasToArrayAltText); 484 registerAlias(NS_PNG, "CreationTime", NS_XMP, "CreateDate", null); 485 registerAlias(NS_PNG, "Description", NS_DC, "description", aliasToArrayAltText); 486 registerAlias(NS_PNG, "ModificationTime", NS_XMP, "ModifyDate", null); 487 registerAlias(NS_PNG, "Software", NS_XMP, "CreatorTool", null); 488 registerAlias(NS_PNG, "Title", NS_DC, "title", aliasToArrayAltText); 489 } 490 }