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
019package org.apache.hadoop.registry.client.types;
020
021import com.google.common.base.Preconditions;
022import org.apache.hadoop.classification.InterfaceAudience;
023import org.apache.hadoop.classification.InterfaceStability;
024import org.apache.hadoop.registry.client.binding.JsonSerDeser;
025import org.apache.hadoop.registry.client.binding.RegistryTypeUtils;
026import org.codehaus.jackson.annotate.JsonIgnoreProperties;
027import org.codehaus.jackson.map.annotate.JsonSerialize;
028
029import java.net.URI;
030import java.util.ArrayList;
031import java.util.HashMap;
032import java.util.List;
033import 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)
050public 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}