1616 */
1717package org .apache .jackrabbit .commons ;
1818
19+ import java .math .BigInteger ;
20+ import java .nio .charset .StandardCharsets ;
21+ import java .security .MessageDigest ;
22+ import java .security .NoSuchAlgorithmException ;
1923import java .util .HashMap ;
24+ import java .util .Locale ;
2025import java .util .Map ;
26+ import java .util .UUID ;
27+ import java .util .function .UnaryOperator ;
2128
2229import javax .jcr .NamespaceException ;
2330import javax .jcr .NamespaceRegistry ;
@@ -220,26 +227,31 @@ public String registerNamespace(String prefix, String uri)
220227 // Check if the namespace is registered
221228 registry .getPrefix (uri );
222229 } catch (NamespaceException e1 ) {
223- // Replace troublesome prefix hints
230+ // Throw away Troublesome prefix hints
224231 if (prefix == null || prefix .isEmpty ()
225232 || prefix .toLowerCase ().startsWith ("xml" )
226233 || !XMLChar .isValidNCName (prefix )) {
227- prefix = "ns" ; // ns, ns2, ns3, ns4, ...
234+ prefix = null ;
228235 }
229236
230- // Loop until an unused prefix is found
231- try {
232- String base = prefix ;
233- for ( int i = 2 ; true ; i ++) {
234- registry .getURI (prefix );
235- prefix = base + i ;
236- }
237- } catch ( NamespaceException e2 ) {
238- // Exit the loop
239- }
237+ if ( prefix == null ) {
238+ prefix = suggestPrefix ( uri , pref -> {
239+ // prefix checker
240+ try {
241+ return registry .getURI (pref );
242+ } catch ( RepositoryException e ) {
243+ return null ;
244+ }
245+ });
246+ }
240247
241248 // Register the namespace
242- registry .registerNamespace (prefix , uri );
249+ try {
250+ registry .registerNamespace (prefix , uri );
251+ } catch (NamespaceException ex ) {
252+ // likely prefix is already in use; retry with null prefix
253+ return registerNamespace (null , uri );
254+ }
243255 }
244256
245257 return session .getNamespacePrefix (uri );
@@ -258,4 +270,113 @@ public void registerNamespaces(Map<String,String> namespaces) throws RepositoryE
258270 }
259271 }
260272
273+ // non-public supporting code
274+
275+ // map with 'optimal' prefix mappings; hard-wired should be ok for now
276+ private static final Map <String , String > KNOWN_PREFIXES =
277+ Map .of ("http://creativecommons.org/ns#" , "cc" ,
278+ "http://purl.org/dc/terms/" , "dc" ,
279+ "http://ns.adobe.com/DICOM/" , "DICOM" ,
280+ "http://ns.adobe.com/exif/1.0/" , "exif" ,
281+ "http://ns.adobe.com/pdf/1.3/" , "pdf" ,
282+ "http://ns.adobe.com/pdfx/1.3/" , "pdfx" ,
283+ "http://ns.adobe.com/photoshop/1.0/" , "photoshop" ,
284+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#" , "rdf" ,
285+ "http://ns.adobe.com/tiff/1.0/" , "tiff" ,
286+ "http://ns.adobe.com/xap/1.0/" , "xmp" );
287+
288+
289+ // suggest an available prefix for the provided namespace, based on a random UUID
290+ // (last resort)
291+ private static String devisePrefixByUUID (String namespace , UnaryOperator <String > lookupNamespace ) {
292+ String prefix ;
293+
294+ do {
295+ prefix = "u-" + UUID .randomUUID ();
296+ } while (namespace .equals (lookupNamespace .apply (prefix )));
297+
298+ return prefix ;
299+ }
300+
301+ // compute SHA-256, null when NoSuchAlgorithmException
302+ private static String getSha256 (String namespace ) {
303+ try {
304+ byte [] bytes = MessageDigest .getInstance ("SHA-256" ).digest (namespace .getBytes (StandardCharsets .UTF_8 ));
305+ return new BigInteger (1 , bytes ).toString (16 );
306+ } catch (NoSuchAlgorithmException e ) {
307+ // this really, really should not happen
308+ return null ;
309+ }
310+ }
311+
312+ // suggest an available prefix for the provided namespace, based on the sha-256
313+ // of the namespace name, and a final fallback to UUID based (while considering pre-existing mappings)
314+ private static String devisePrefixTrySha256 (String namespace , UnaryOperator <String > lookupNamespace ) {
315+
316+ String sha = getSha256 (namespace );
317+ if (sha != null ) {
318+ for (int i = 7 ; i <= sha .length (); i ++) {
319+ String prefix = "s-" + sha .substring (0 , i );
320+ String lookedUpPrefix = lookupNamespace .apply (prefix );
321+ if (lookedUpPrefix == null ) {
322+ // unused, so go ahead with this prefix
323+ return prefix ;
324+ }
325+ }
326+ }
327+
328+ // fallback to UUID
329+ return devisePrefixByUUID (namespace , lookupNamespace );
330+ }
331+
332+ // suggest an available prefix for the provided namespace, based on the characters
333+ // in the namespace name (while considering pre-existing mappings)
334+ private static String devisePrefix (String namespace , UnaryOperator <String > lookupNamespace ) {
335+ String prefix = namespace .toLowerCase (Locale .ENGLISH );
336+
337+ // strip scheme when http(s)
338+ if (prefix .startsWith ("http://" )) {
339+ prefix = prefix .substring ("http://" .length ());
340+ } else if (prefix .startsWith ("https://" )) {
341+ prefix = prefix .substring ("https://" .length ());
342+ }
343+
344+ // strip common host name prefixes
345+ if (prefix .startsWith ("www." )) {
346+ prefix = prefix .substring ("www." .length ());
347+ } else if (prefix .startsWith ("ns." )) {
348+ prefix = prefix .substring ("ns." .length ());
349+ }
350+
351+ // replace characters not allowed in prefix (here: '\', '/' and :)
352+ prefix = prefix .replaceAll ("[\\ /:]+" , "-" );
353+
354+ // strip trailing replacement character
355+ while (prefix .endsWith ("-" )) {
356+ prefix = prefix .substring (0 , prefix .length () - 1 );
357+ }
358+
359+ String lookedUpNamespace = lookupNamespace .apply (prefix );
360+ if (lookedUpNamespace == null || lookedUpNamespace .equals (namespace )) {
361+ return prefix ;
362+ } else {
363+ return devisePrefixTrySha256 (namespace , lookupNamespace );
364+ }
365+ }
366+
367+ // suggest an available prefix for the provided namespace (while considering pre-existing mappings)
368+ private static String suggestPrefix (String namespace , UnaryOperator <String > lookupPrefix ) {
369+ // try hard-wired map
370+ String known = KNOWN_PREFIXES .get (namespace );
371+
372+ // lookup using supplied mapper as well
373+ String lookedUpNamespace = known != null ? lookupPrefix .apply (known ) : null ;
374+
375+ // return hardwired prefix if unused or mapper has the prefix mapped to the same namespace
376+ if (known != null && (lookedUpNamespace == null || namespace .equals (lookedUpNamespace ))) {
377+ return known ;
378+ } else {
379+ return devisePrefix (namespace , lookupPrefix );
380+ }
381+ }
261382}
0 commit comments