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.exceptions.InvalidRecordException;
025    import org.codehaus.jackson.annotate.JsonAnyGetter;
026    import org.codehaus.jackson.annotate.JsonAnySetter;
027    import org.codehaus.jackson.map.annotate.JsonSerialize;
028    
029    import java.util.ArrayList;
030    import java.util.HashMap;
031    import java.util.List;
032    import 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)
042    public 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    }