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.types;
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.binding.JsonSerDeser;
025    import org.apache.hadoop.registry.client.binding.RegistryTypeUtils;
026    import org.codehaus.jackson.annotate.JsonIgnoreProperties;
027    import org.codehaus.jackson.map.annotate.JsonSerialize;
028    
029    import java.net.URI;
030    import java.util.ArrayList;
031    import java.util.HashMap;
032    import java.util.List;
033    import java.util.Map;
034    
035    /**
036     * Description of a single service/component endpoint.
037     * It is designed to be marshalled as JSON.
038     * <p>
039     * Every endpoint can have more than one address entry, such as
040     * a list of URLs to a replicated service, or a (hostname, port)
041     * pair. Each of these address entries is represented as a string list,
042     * as that is the only reliably marshallable form of a tuple JSON can represent.
043     *
044     *
045     */
046    @InterfaceAudience.Public
047    @InterfaceStability.Evolving
048    @JsonIgnoreProperties(ignoreUnknown = true)
049    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
050    public final class Endpoint implements Cloneable {
051    
052      /**
053       * API implemented at the end of the binding
054       */
055      public String api;
056    
057      /**
058       * Type of address. The standard types are defined in
059       * {@link AddressTypes}
060       */
061      public String addressType;
062    
063      /**
064       * Protocol type. Some standard types are defined in
065       * {@link ProtocolTypes}
066       */
067      public String protocolType;
068    
069      /**
070       * a list of address tuples —tuples whose format depends on the address type
071       */
072      public List<Map<String, String>> addresses;
073    
074      /**
075       * Create an empty instance.
076       */
077      public Endpoint() {
078      }
079    
080      /**
081       * Create an endpoint from another endpoint.
082       * This is a deep clone with a new list of addresses.
083       * @param that the endpoint to copy from
084       */
085      public Endpoint(Endpoint that) {
086        this.api = that.api;
087        this.addressType = that.addressType;
088        this.protocolType = that.protocolType;
089        this.addresses = newAddresses(that.addresses.size());
090        for (Map<String, String> address : that.addresses) {
091          Map<String, String> addr2 = new HashMap<String, String>(address.size());
092          addr2.putAll(address);
093          addresses.add(addr2);
094        }
095      }
096    
097      /**
098       * Build an endpoint with a list of addresses
099       * @param api API name
100       * @param addressType address type
101       * @param protocolType protocol type
102       * @param addrs addresses
103       */
104      public Endpoint(String api,
105          String addressType,
106          String protocolType,
107          List<Map<String, String>> addrs) {
108        this.api = api;
109        this.addressType = addressType;
110        this.protocolType = protocolType;
111        this.addresses = newAddresses(0);
112        if (addrs != null) {
113          addresses.addAll(addrs);
114        }
115      }
116    
117      /**
118       * Build an endpoint with an empty address list
119       * @param api API name
120       * @param addressType address type
121       * @param protocolType protocol type
122       */
123      public Endpoint(String api,
124          String addressType,
125          String protocolType) {
126        this.api = api;
127        this.addressType = addressType;
128        this.protocolType = protocolType;
129        this.addresses = newAddresses(0);
130      }
131    
132      /**
133       * Build an endpoint with a single address entry.
134       * <p>
135       * This constructor is superfluous given the varags constructor is equivalent
136       * for a single element argument. However, type-erasure in java generics
137       * causes javac to warn about unchecked generic array creation. This
138       * constructor, which represents the common "one address" case, does
139       * not generate compile-time warnings.
140       * @param api API name
141       * @param addressType address type
142       * @param protocolType protocol type
143       * @param addr address. May be null —in which case it is not added
144       */
145      public Endpoint(String api,
146          String addressType,
147          String protocolType,
148          Map<String, String> addr) {
149        this(api, addressType, protocolType);
150        if (addr != null) {
151          addresses.add(addr);
152        }
153      }
154    
155      /**
156       * Build an endpoint with a list of addresses
157       * @param api API name
158       * @param addressType address type
159       * @param protocolType protocol type
160       * @param addrs addresses. Null elements will be skipped
161       */
162      public Endpoint(String api,
163          String addressType,
164          String protocolType,
165          Map<String, String>...addrs) {
166        this(api, addressType, protocolType);
167        for (Map<String, String> addr : addrs) {
168          if (addr!=null) {
169            addresses.add(addr);
170          }
171        }
172      }
173    
174      /**
175       * Create a new address structure of the requested size
176       * @param size size to create
177       * @return the new list
178       */
179      private List<Map<String, String>> newAddresses(int size) {
180        return new ArrayList<Map<String, String>>(size);
181      }
182    
183      /**
184       * Build an endpoint from a list of URIs; each URI
185       * is ASCII-encoded and added to the list of addresses.
186       * @param api API name
187       * @param protocolType protocol type
188       * @param uris URIs to convert to a list of tup;les
189       */
190      public Endpoint(String api,
191          String protocolType,
192          URI... uris) {
193        this.api = api;
194        this.addressType = AddressTypes.ADDRESS_URI;
195    
196        this.protocolType = protocolType;
197        List<Map<String, String>> addrs = newAddresses(uris.length);
198        for (URI uri : uris) {
199          addrs.add(RegistryTypeUtils.uri(uri.toString()));
200        }
201        this.addresses = addrs;
202      }
203    
204      @Override
205      public String toString() {
206          return marshalToString.toString(this);
207      }
208    
209      /**
210       * Validate the record by checking for null fields and other invalid
211       * conditions
212       * @throws NullPointerException if a field is null when it
213       * MUST be set.
214       * @throws RuntimeException on invalid entries
215       */
216      public void validate() {
217        Preconditions.checkNotNull(api, "null API field");
218        Preconditions.checkNotNull(addressType, "null addressType field");
219        Preconditions.checkNotNull(protocolType, "null protocolType field");
220        Preconditions.checkNotNull(addresses, "null addresses field");
221        for (Map<String, String> address : addresses) {
222          Preconditions.checkNotNull(address, "null element in address");
223        }
224      }
225    
226      /**
227       * Shallow clone: the lists of addresses are shared
228       * @return a cloned instance
229       * @throws CloneNotSupportedException
230       */
231      @Override
232      public Object clone() throws CloneNotSupportedException {
233        return super.clone();
234      }
235    
236    
237      /**
238       * Static instance of service record marshalling
239       */
240      private static class Marshal extends JsonSerDeser<Endpoint> {
241        private Marshal() {
242          super(Endpoint.class);
243        }
244      }
245    
246      private static final Marshal marshalToString = new Marshal();
247    }