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 */ 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 */ 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 */ 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 */ 140 public synchronized String getNamespacePrefix(String namespaceURI) 141 { 142 return (String) namespaceToPrefixMap.get(namespaceURI); 143 } 144 145 146 /** 147 * @see XMPSchemaRegistry#getNamespaceURI(String) 148 */ 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 */ 162 public synchronized Map getNamespaces() 163 { 164 return Collections.unmodifiableMap(new TreeMap(namespaceToPrefixMap)); 165 } 166 167 168 /** 169 * @see XMPSchemaRegistry#getPrefixes() 170 */ 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 */ 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 */ 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 */ 268 public synchronized XMPAliasInfo findAlias(String qname) 269 { 270 return (XMPAliasInfo) aliasMap.get(qname); 271 } 272 273 274 /** 275 * @see XMPSchemaRegistry#findAliases(String) 276 */ 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 */ 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 */ 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 */ 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 }