001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.registry.client.binding;
020    
021    import com.google.common.base.Preconditions;
022    import org.apache.hadoop.classification.InterfaceAudience;
023    import org.apache.hadoop.classification.InterfaceStability;
024    import org.apache.hadoop.registry.client.exceptions.InvalidRecordException;
025    import static org.apache.hadoop.registry.client.types.AddressTypes.*;
026    import org.apache.hadoop.registry.client.types.Endpoint;
027    import org.apache.hadoop.registry.client.types.ProtocolTypes;
028    import org.apache.hadoop.registry.client.types.ServiceRecord;
029    
030    import java.net.InetSocketAddress;
031    import java.net.MalformedURLException;
032    import java.net.URI;
033    import java.net.URL;
034    import java.util.ArrayList;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    
039    /**
040     * Static methods to work with registry types —primarily endpoints and the
041     * list representation of addresses.
042     */
043    @InterfaceAudience.Public
044    @InterfaceStability.Evolving
045    public class RegistryTypeUtils {
046    
047      /**
048       * Create a URL endpoint from a list of URIs
049       * @param api implemented API
050       * @param protocolType protocol type
051       * @param uris URIs
052       * @return a new endpoint
053       */
054      public static Endpoint urlEndpoint(String api,
055          String protocolType,
056          URI... uris) {
057        return new Endpoint(api, protocolType, uris);
058      }
059    
060      /**
061       * Create a REST endpoint from a list of URIs
062       * @param api implemented API
063       * @param uris URIs
064       * @return a new endpoint
065       */
066      public static Endpoint restEndpoint(String api,
067          URI... uris) {
068        return urlEndpoint(api, ProtocolTypes.PROTOCOL_REST, uris);
069      }
070    
071      /**
072       * Create a Web UI endpoint from a list of URIs
073       * @param api implemented API
074       * @param uris URIs
075       * @return a new endpoint
076       */
077      public static Endpoint webEndpoint(String api,
078          URI... uris) {
079        return urlEndpoint(api, ProtocolTypes.PROTOCOL_WEBUI, uris);
080      }
081    
082      /**
083       * Create an internet address endpoint from a list of URIs
084       * @param api implemented API
085       * @param protocolType protocol type
086       * @param hostname hostname/FQDN
087       * @param port port
088       * @return a new endpoint
089       */
090    
091      public static Endpoint inetAddrEndpoint(String api,
092          String protocolType,
093          String hostname,
094          int port) {
095        Preconditions.checkArgument(api != null, "null API");
096        Preconditions.checkArgument(protocolType != null, "null protocolType");
097        Preconditions.checkArgument(hostname != null, "null hostname");
098        return new Endpoint(api,
099            ADDRESS_HOSTNAME_AND_PORT,
100            protocolType,
101            hostnamePortPair(hostname, port));
102      }
103    
104      /**
105       * Create an IPC endpoint
106       * @param api API
107       * @param address the address as a tuple of (hostname, port)
108       * @return the new endpoint
109       */
110      public static Endpoint ipcEndpoint(String api, InetSocketAddress address) {
111        return new Endpoint(api,
112            ADDRESS_HOSTNAME_AND_PORT,
113            ProtocolTypes.PROTOCOL_HADOOP_IPC,
114            address== null ? null: hostnamePortPair(address));
115      }
116    
117      /**
118       * Create a single entry map
119       * @param key map entry key
120       * @param val map entry value
121       * @return a 1 entry map.
122       */
123      public static Map<String, String> map(String key, String val) {
124        Map<String, String> map = new HashMap<String, String>(1);
125        map.put(key, val);
126        return map;
127      }
128    
129      /**
130       * Create a URI
131       * @param uri value
132       * @return a 1 entry map.
133       */
134      public static Map<String, String> uri(String uri) {
135        return map(ADDRESS_URI, uri);
136      }
137    
138      /**
139       * Create a (hostname, port) address pair
140       * @param hostname hostname
141       * @param port port
142       * @return a 1 entry map.
143       */
144      public static Map<String, String> hostnamePortPair(String hostname, int port) {
145        Map<String, String> map =
146            map(ADDRESS_HOSTNAME_FIELD, hostname);
147        map.put(ADDRESS_PORT_FIELD, Integer.toString(port));
148        return map;
149      }
150    
151      /**
152       * Create a (hostname, port) address pair
153       * @param address socket address whose hostname and port are used for the
154       * generated address.
155       * @return a 1 entry map.
156       */
157      public static Map<String, String> hostnamePortPair(InetSocketAddress address) {
158        return hostnamePortPair(address.getHostName(), address.getPort());
159      }
160    
161      /**
162       * Require a specific address type on an endpoint
163       * @param required required type
164       * @param epr endpoint
165       * @throws InvalidRecordException if the type is wrong
166       */
167      public static void requireAddressType(String required, Endpoint epr) throws
168          InvalidRecordException {
169        if (!required.equals(epr.addressType)) {
170          throw new InvalidRecordException(
171              epr.toString(),
172              "Address type of " + epr.addressType
173              + " does not match required type of "
174              + required);
175        }
176      }
177    
178      /**
179       * Get a single URI endpoint
180       * @param epr endpoint
181       * @return the uri of the first entry in the address list. Null if the endpoint
182       * itself is null
183       * @throws InvalidRecordException if the type is wrong, there are no addresses
184       * or the payload ill-formatted
185       */
186      public static List<String> retrieveAddressesUriType(Endpoint epr)
187          throws InvalidRecordException {
188        if (epr == null) {
189          return null;
190        }
191        requireAddressType(ADDRESS_URI, epr);
192        List<Map<String, String>> addresses = epr.addresses;
193        if (addresses.size() < 1) {
194          throw new InvalidRecordException(epr.toString(),
195              "No addresses in endpoint");
196        }
197        List<String> results = new ArrayList<String>(addresses.size());
198        for (Map<String, String> address : addresses) {
199          results.add(getAddressField(address, ADDRESS_URI));
200        }
201        return results;
202      }
203    
204      /**
205       * Get a specific field from an address -raising an exception if
206       * the field is not present
207       * @param address address to query
208       * @param field field to resolve
209       * @return the resolved value. Guaranteed to be non-null.
210       * @throws InvalidRecordException if the field did not resolve
211       */
212      public static String getAddressField(Map<String, String> address,
213          String field) throws InvalidRecordException {
214        String val = address.get(field);
215        if (val == null) {
216          throw new InvalidRecordException("", "Missing address field: " + field);
217        }
218        return val;
219      }
220    
221      /**
222       * Get the address URLs. Guranteed to return at least one address.
223       * @param epr endpoint
224       * @return the address as a URL
225       * @throws InvalidRecordException if the type is wrong, there are no addresses
226       * or the payload ill-formatted
227       * @throws MalformedURLException address can't be turned into a URL
228       */
229      public static List<URL> retrieveAddressURLs(Endpoint epr)
230          throws InvalidRecordException, MalformedURLException {
231        if (epr == null) {
232          throw new InvalidRecordException("", "Null endpoint");
233        }
234        List<String> addresses = retrieveAddressesUriType(epr);
235        List<URL> results = new ArrayList<URL>(addresses.size());
236        for (String address : addresses) {
237          results.add(new URL(address));
238        }
239        return results;
240      }
241    
242      /**
243       * Validate the record by checking for null fields and other invalid
244       * conditions
245       * @param path path for exceptions
246       * @param record record to validate. May be null
247       * @throws InvalidRecordException on invalid entries
248       */
249      public static void validateServiceRecord(String path, ServiceRecord record)
250          throws InvalidRecordException {
251        if (record == null) {
252          throw new InvalidRecordException(path, "Null record");
253        }
254        if (!ServiceRecord.RECORD_TYPE.equals(record.type)) {
255          throw new InvalidRecordException(path,
256              "invalid record type field: \"" + record.type + "\"");
257        }
258    
259        if (record.external != null) {
260          for (Endpoint endpoint : record.external) {
261            validateEndpoint(path, endpoint);
262          }
263        }
264        if (record.internal != null) {
265          for (Endpoint endpoint : record.internal) {
266            validateEndpoint(path, endpoint);
267          }
268        }
269      }
270    
271      /**
272       * Validate the endpoint by checking for null fields and other invalid
273       * conditions
274       * @param path path for exceptions
275       * @param endpoint endpoint to validate. May be null
276       * @throws InvalidRecordException on invalid entries
277       */
278      public static void validateEndpoint(String path, Endpoint endpoint)
279          throws InvalidRecordException {
280        if (endpoint == null) {
281          throw new InvalidRecordException(path, "Null endpoint");
282        }
283        try {
284          endpoint.validate();
285        } catch (RuntimeException e) {
286          throw new InvalidRecordException(path, e.toString());
287        }
288      }
289    
290    }