View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.api.util;
18  
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import org.forgerock.api.models.ApiDescription;
25  import org.forgerock.api.models.ApiError;
26  import org.forgerock.api.models.Reference;
27  import org.forgerock.api.models.Resource;
28  import org.forgerock.api.models.Schema;
29  import org.forgerock.util.Reject;
30  
31  /**
32   * Helper that registers one or more {@link ApiDescription} instances and provides a means to resolve
33   * {@link Reference}s.
34   */
35  public class ReferenceResolver {
36  
37      private static final String DEFINITIONS_REF = "#/definitions/";
38  
39      private static final String ERRORS_REF = "#/errors/";
40  
41      private static final String SERVICES_REF = "#/services/";
42  
43      private final ApiDescription local;
44  
45      private final Map<String, ApiDescription> map;
46  
47      /**
48       * Creates a reference-resolver and defines the one {@link ApiDescription} that can be used for local
49       * (non-namespaced) reference lookups.
50       *
51       * @param local {@link ApiDescription} to use for local (non-namespaced) reference lookups
52       */
53      public ReferenceResolver(final ApiDescription local) {
54          this.local = Reject.checkNotNull(local);
55          map = new HashMap<>();
56          register(local);
57      }
58  
59      /**
60       * Registers an external {@link ApiDescription}, for {@link org.forgerock.api.models.Reference} lookup, and
61       * must not have previously been registered.
62       *
63       * @param apiDescription {@link ApiDescription} to register, which has not previously been registered
64       * @return self
65       */
66      public ReferenceResolver register(final ApiDescription apiDescription) {
67          if (map.containsKey(apiDescription.getId())) {
68              throw new IllegalStateException("Already registered ID = " + apiDescription.getId());
69          }
70          map.put(apiDescription.getId(), apiDescription);
71          return this;
72      }
73  
74      /**
75       * Registers external {@link ApiDescription}s, for {@link org.forgerock.api.models.Reference} lookup, and each
76       * must not have previously been registered.
77       *
78       * @param apiDescriptions List of {@link ApiDescription}s to register, which have not previously been registered
79       * @return self
80       */
81      public ReferenceResolver registerAll(final ApiDescription... apiDescriptions) {
82          for (final ApiDescription item : apiDescriptions) {
83              register(item);
84          }
85          return this;
86      }
87  
88      /**
89       * Gets a {@link org.forgerock.api.models.Definitions} {@link Schema} by JSON reference.
90       *
91       * @param reference JSON reference
92       * @return {@link Schema} or {@code null} if not found
93       */
94      public Schema getDefinition(final Reference reference) {
95          return resolveDefinition(reference, new HashSet<String>());
96      }
97  
98      private Schema resolveDefinition(final Reference reference, final Set<String> visitedRefs) {
99          final int nameStart = reference.getValue().indexOf(DEFINITIONS_REF);
100         if (nameStart != -1) {
101             final String name = reference.getValue().substring(nameStart + DEFINITIONS_REF.length());
102             if (!name.isEmpty()) {
103                 if (!visitedRefs.add(reference.getValue())) {
104                     throw new IllegalStateException("Reference loop detected: " + reference.getValue());
105                 }
106                 if (nameStart == 0) {
107                     // there is no namespace, so do a local lookup
108                     if (local.getDefinitions() != null) {
109                         final Schema schema = local.getDefinitions().get(name);
110                         if (schema != null && schema.getReference() != null) {
111                             // reference chain
112                             return resolveDefinition(schema.getReference(), visitedRefs);
113                         }
114                         return schema;
115                     }
116                 } else {
117                     final String namespace = reference.getValue().substring(0, nameStart);
118                     final ApiDescription apiDescription = map.get(namespace);
119                     if (apiDescription != null && apiDescription.getDefinitions() != null) {
120                         final Schema schema = apiDescription.getDefinitions().get(name);
121                         if (schema != null && schema.getReference() != null) {
122                             // reference chain
123                             return resolveDefinition(schema.getReference(), visitedRefs);
124                         }
125                         return schema;
126                     }
127                 }
128             }
129         }
130         return null;
131     }
132 
133     /**
134      * Gets and {@link org.forgerock.api.models.Errors} {@link ApiError} by JSON reference.
135      *
136      * @param reference JSON reference
137      * @return {@link ApiError} or {@code null} if not found
138      */
139     public ApiError getError(final Reference reference) {
140         return resolveError(reference, new HashSet<String>());
141     }
142 
143     private ApiError resolveError(final Reference reference, final Set<String> visitedRefs) {
144         final int nameStart = reference.getValue().indexOf(ERRORS_REF);
145         if (nameStart != -1) {
146             final String name = reference.getValue().substring(nameStart + ERRORS_REF.length());
147             if (!name.isEmpty()) {
148                 if (!visitedRefs.add(reference.getValue())) {
149                     throw new IllegalStateException("Reference loop detected: " + reference.getValue());
150                 }
151                 if (nameStart == 0) {
152                     // there is no namespace, so do a local lookup
153                     if (local.getErrors() != null) {
154                         final ApiError error = local.getErrors().get(name);
155                         if (error != null && error.getReference() != null) {
156                             // reference chain
157                             return resolveError(error.getReference(), visitedRefs);
158                         }
159                         return error;
160                     }
161                 } else {
162                     final String namespace = reference.getValue().substring(0, nameStart);
163                     final ApiDescription apiDescription = map.get(namespace);
164                     if (apiDescription != null && apiDescription.getErrors() != null) {
165                         final ApiError error = apiDescription.getErrors().get(name);
166                         if (error != null && error.getReference() != null) {
167                             // reference chain
168                             return resolveError(error.getReference(), visitedRefs);
169                         }
170                         return error;
171                     }
172                 }
173             }
174         }
175         return null;
176     }
177 
178     /**
179      * Get a {@link org.forgerock.api.models.Services} {@link Resource} by JSON reference.
180      *
181      * @param reference JSON reference
182      * @return {@link Resource} or {@code null} if not found
183      */
184     public Resource getService(final Reference reference) {
185         return resolveService(reference, new HashSet<String>());
186     }
187 
188     private Resource resolveService(final Reference reference, final Set<String> visitedRefs) {
189         final int nameStart = reference.getValue().indexOf(SERVICES_REF);
190         if (nameStart != -1) {
191             final String name = reference.getValue().substring(nameStart + SERVICES_REF.length());
192             if (!name.isEmpty()) {
193                 if (!visitedRefs.add(reference.getValue())) {
194                     throw new IllegalStateException("Reference loop detected: " + reference.getValue());
195                 }
196                 if (nameStart == 0) {
197                     // there is no namespace, so do a local lookup
198                     if (local.getServices() != null) {
199                         final Resource service = local.getServices().get(name);
200                         if (service != null && service.getReference() != null) {
201                             // reference chain
202                             return resolveService(service.getReference(), visitedRefs);
203                         }
204                         return service;
205                     }
206                 } else {
207                     final String namespace = reference.getValue().substring(0, nameStart);
208                     final ApiDescription apiDescription = map.get(namespace);
209                     if (apiDescription != null && apiDescription.getServices() != null) {
210                         final Resource service = apiDescription.getServices().get(name);
211                         if (service != null && service.getReference() != null) {
212                             // reference chain
213                             return resolveService(service.getReference(), visitedRefs);
214                         }
215                         return service;
216                     }
217                 }
218             }
219         }
220         return null;
221     }
222 }