001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bean.jsonschema; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021import static org.apache.juneau.commons.utils.ThrowableUtils.*; 022 023import java.io.*; 024import java.net.*; 025import java.util.concurrent.*; 026 027import org.apache.juneau.marshall.*; 028import org.apache.juneau.marshall.marshaller.*; 029 030/** 031 * A container for retrieving JSON {@link JsonSchema} objects by URI. 032 * 033 * <p> 034 * Subclasses must implement one of the following methods to load schemas from external sources: 035 * <ul class='spaced-list'> 036 * <li> 037 * {@link #getReader(URI)} - If schemas should be loaded from readers and automatically parsed. 038 * <li> 039 * {@link #load(URI)} - If you want control over construction of {@link JsonSchema} objects. 040 * </ul> 041 * 042 * @serial exclude 043 */ 044public abstract class JsonSchemaMap extends ConcurrentHashMap<URI,JsonSchema<?>> { 045 046 private static final long serialVersionUID = 1L; 047 048 /** 049 * Convenience method for pre-populating this map with the specified schemas. 050 * 051 * <p> 052 * The schemas passed in through this method MUST have their ID properties set. 053 * 054 * @param schemas The set of schemas to add to this map. 055 * @return This object. 056 * @throws RuntimeException If one or more schema objects did not have their ID property set. 057 */ 058 @SuppressWarnings({ 059 "removal" // Uses deprecated API for compatibility 060 }) 061 public JsonSchemaMap add(JsonSchema<?>...schemas) { 062 for (var schema : schemas) { 063 if (schema.getId() == null) 064 throw iaex("Schema with no ID passed to JsonSchemaMap.add(Schema...)"); 065 put(schema.getId(), schema); 066 schema.setSchemaMap(this); 067 } 068 return this; 069 } 070 071 /** 072 * Return the {@link JsonSchema} object at the specified URI. 073 * 074 * <p> 075 * If this schema object has not been loaded yet, calls {@link #load(URI)}. 076 * 077 * <p> 078 * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}. 079 * Strings must be valid URIs. 080 * 081 * <p> 082 * URIs defined by {@link UriResolver} can be used for values. 083 * 084 * @param uri The URI of the schema to retrieve. Must not be <jk>null</jk>. 085 * @return The JsonSchema, or <jk>null</jk> if schema was not located and could not be loaded. 086 */ 087 @Override /* Overridden from Map */ 088 public JsonSchema<?> get(Object uri) { 089 var u = toUri(uri); 090 var s = super.get(u); 091 if (nn(s)) 092 return s; 093 synchronized (this) { 094 s = load(u); 095 if (nn(s)) { 096 // Note: Can't use add(Schema...) since the ID property may not be set. 097 s.setSchemaMap(this); 098 put(u, s); 099 } 100 return s; 101 } 102 } 103 104 /** 105 * Subclasses must implement either this method or {@link #load(URI)} to load the schema with the specified URI. 106 * 107 * <p> 108 * It's up to the implementer to decide where these come from. 109 * 110 * <p> 111 * The default implementation returns <jk>null</jk>. 112 * 113 * @param uri The URI to connect to and retrieve the contents. 114 * @return The reader from reading the specified URI, or <jk>null</jk> if the document is unreachable (the default implementation always returns <jk>null</jk>). 115 */ 116 public Reader getReader(URI uri) { 117 return null; 118 } 119 120 /** 121 * Subclasses must implement either this method or {@link #getReader(URI)} to load the schema with the specified URI. 122 * 123 * <p> 124 * It's up to the implementer to decide where these come from. 125 * 126 * <p> 127 * The default implementation calls {@link #getReader(URI)} and parses the schema document. 128 * If {@link #getReader(URI)} returns <jk>null</jk>, this method returns <jk>null</jk> indicating this is an 129 * unreachable document. 130 * 131 * @param uri The URI to load the schema from. 132 * @return The parsed schema, or <jk>null</jk> if the document is unreachable. 133 */ 134 @SuppressWarnings({ 135 "java:S1452" // Self-bounded (CRTP) generic: the loaded schema's concrete SELF subtype is not known here, so JsonSchema<?> is the only sound return type. 136 }) 137 public JsonSchema<?> load(URI uri) { 138 try (var r = getReader(uri)) { 139 if (r == null) 140 return null; 141 return Json5.DEFAULT.read(r, JsonSchema.class); 142 } catch (Exception e) { // HTT - compiler-generated branch from try-with-resources null check 143 throw toRex(e); 144 } 145 } 146}