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.exceptions.InvalidRecordException;
025import org.codehaus.jackson.annotate.JsonAnyGetter;
026import org.codehaus.jackson.annotate.JsonAnySetter;
027import org.codehaus.jackson.map.annotate.JsonSerialize;
028
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034/**
035 * JSON-marshallable description of a single component.
036 * It supports the deserialization of unknown attributes, but does
037 * not support their creation.
038 */
039@InterfaceAudience.Public
040@InterfaceStability.Evolving
041@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
042public class ServiceRecord implements Cloneable {
043
044  /**
045   * A type string which MUST be in the serialized json. This permits
046   * fast discarding of invalid entries
047   */
048  public static final String RECORD_TYPE = "JSONServiceRecord";
049
050  /**
051   * The type field. This must be the string {@link #RECORD_TYPE}
052   */
053  public String type = RECORD_TYPE;
054
055  /**
056   * Description string
057   */
058  public String description;
059
060  /**
061   * map to handle unknown attributes.
062   */
063  private Map<String, String> attributes = new HashMap<String, String>(4);
064
065  /**
066   * List of endpoints intended for use to external callers
067   */
068  public List<Endpoint> external = new ArrayList<Endpoint>();
069
070  /**
071   * List of endpoints for use <i>within</i> an application.
072   */
073  public List<Endpoint> internal = new ArrayList<Endpoint>();
074
075  /**
076   * Create a service record with no ID, description or registration time.
077   * Endpoint lists are set to empty lists.
078   */
079  public ServiceRecord() {
080  }
081
082  /**
083   * Deep cloning constructor
084   * @param that service record source
085   */
086  public ServiceRecord(ServiceRecord that) {
087    this.description = that.description;
088    // others
089    Map<String, String> thatAttrs = that.attributes;
090    for (Map.Entry<String, String> entry : thatAttrs.entrySet()) {
091      attributes.put(entry.getKey(), entry.getValue());
092    }
093    // endpoints
094    List<Endpoint> src = that.internal;
095    if (src != null) {
096      internal = new ArrayList<Endpoint>(src.size());
097      for (Endpoint endpoint : src) {
098        internal.add(new Endpoint(endpoint));
099      }
100    }
101    src = that.external;
102    if (src != null) {
103      external = new ArrayList<Endpoint>(src.size());
104      for (Endpoint endpoint : src) {
105        external.add(new Endpoint(endpoint));
106      }
107    }
108  }
109
110  /**
111   * Add an external endpoint
112   * @param endpoint endpoint to set
113   */
114  public void addExternalEndpoint(Endpoint endpoint) {
115    Preconditions.checkArgument(endpoint != null);
116    endpoint.validate();
117    external.add(endpoint);
118  }
119
120  /**
121   * Add an internal endpoint
122   * @param endpoint endpoint to set
123   */
124  public void addInternalEndpoint(Endpoint endpoint) {
125    Preconditions.checkArgument(endpoint != null);
126    endpoint.validate();
127    internal.add(endpoint);
128  }
129
130  /**
131   * Look up an internal endpoint
132   * @param api API
133   * @return the endpoint or null if there was no match
134   */
135  public Endpoint getInternalEndpoint(String api) {
136    return findByAPI(internal, api);
137  }
138
139  /**
140   * Look up an external endpoint
141   * @param api API
142   * @return the endpoint or null if there was no match
143   */
144  public Endpoint getExternalEndpoint(String api) {
145    return findByAPI(external, api);
146  }
147
148  /**
149   * Handle unknown attributes by storing them in the
150   * {@link #attributes} map
151   * @param key attribute name
152   * @param value attribute value.
153   */
154  @JsonAnySetter
155  public void set(String key, Object value) {
156    attributes.put(key, value.toString());
157  }
158
159  /**
160   * The map of "other" attributes set when parsing. These
161   * are not included in the JSON value of this record when it
162   * is generated.
163   * @return a map of any unknown attributes in the deserialized JSON.
164   */
165  @JsonAnyGetter
166  public Map<String, String> attributes() {
167    return attributes;
168  }
169
170  /**
171   * Get the "other" attribute with a specific key
172   * @param key key to look up
173   * @return the value or null
174   */
175  public String get(String key) {
176    return attributes.get(key);
177  }
178
179  /**
180   * Get the "other" attribute with a specific key.
181   * @param key key to look up
182   * @param defVal default value
183   * @return the value as a string,
184   * or <code>defval</code> if the value was not present
185   */
186  public String get(String key, String defVal) {
187    String val = attributes.get(key);
188    return val != null ? val: defVal;
189  }
190
191  /**
192   * Find an endpoint by its API
193   * @param list list
194   * @param api api name
195   * @return the endpoint or null if there was no match
196   */
197  private Endpoint findByAPI(List<Endpoint> list,  String api) {
198    for (Endpoint endpoint : list) {
199      if (endpoint.api.equals(api)) {
200        return endpoint;
201      }
202    }
203    return null;
204  }
205
206  @Override
207  public String toString() {
208    final StringBuilder sb =
209        new StringBuilder("ServiceRecord{");
210    sb.append("description='").append(description).append('\'');
211    sb.append("; external endpoints: {");
212    for (Endpoint endpoint : external) {
213      sb.append(endpoint).append("; ");
214    }
215    sb.append("}; internal endpoints: {");
216    for (Endpoint endpoint : internal) {
217      sb.append(endpoint != null ? endpoint.toString() : "NULL ENDPOINT");
218      sb.append("; ");
219    }
220    sb.append('}');
221
222    if (!attributes.isEmpty()) {
223      sb.append(", attributes: {");
224      for (Map.Entry<String, String> attr : attributes.entrySet()) {
225        sb.append("\"").append(attr.getKey()).append("\"=\"")
226          .append(attr.getValue()).append("\" ");
227      }
228    } else {
229
230      sb.append(", attributes: {");
231    }
232    sb.append('}');
233
234    sb.append('}');
235    return sb.toString();
236  }
237
238  /**
239   * Shallow clone: all endpoints will be shared across instances
240   * @return a clone of the instance
241   * @throws CloneNotSupportedException
242   */
243  @Override
244  protected Object clone() throws CloneNotSupportedException {
245    return super.clone();
246  }
247
248
249}