View Javadoc
1   /*
2    * #%L
3    * ReadPreferenceEditor.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.allanbank.mongodb;
21  
22  import java.beans.PropertyEditorSupport;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import com.allanbank.mongodb.bson.Document;
31  import com.allanbank.mongodb.bson.DocumentAssignable;
32  import com.allanbank.mongodb.bson.Element;
33  import com.allanbank.mongodb.bson.element.DocumentElement;
34  import com.allanbank.mongodb.bson.impl.RootDocument;
35  import com.allanbank.mongodb.bson.json.Json;
36  import com.allanbank.mongodb.error.JsonException;
37  import com.allanbank.mongodb.util.log.Log;
38  import com.allanbank.mongodb.util.log.LogFactory;
39  
40  /**
41   * {@link java.beans.PropertyEditor} for the {@link ReadPreference} class.
42   * <p>
43   * The string value must be one of the following tokens or a parsable JSON
44   * document in the form of {@link ReadPreference#asDocument()}.
45   * </p>
46   * </p> Valid tokens are:
47   * <ul>
48   * <li>{@code PRIMARY}</li>
49   * <li>{@code SECONDARY}</li>
50   * <li>{@code CLOSEST}</li>
51   * <li>{@code NEAREST}</li>
52   * <li>{@code PREFER_PRIMARY}</li>
53   * <li>{@code PREFER_SECONDARY}</li>
54   * </ul>
55   * <p>
56   * This editor will also parse a full MongoDB URI to extract the specified
57   * {@link ReadPreference}. See the <a href=
58   * "http://docs.mongodb.org/manual/reference/connection-string/#read-preference-options"
59   * >Connection String URI Format</a> documentation for information on
60   * constructing a MongoDB URI.
61   * </p>
62   * 
63   * @api.yes This class is part of the driver's API. Public and protected members
64   *          will be deprecated for at least 1 non-bugfix release (version
65   *          numbers are &lt;major&gt;.&lt;minor&gt;.&lt;bugfix&gt;) before being
66   *          removed or modified.
67   * @copyright 2013-2014, Allanbank Consulting, Inc., All Rights Reserved
68   */
69  public class ReadPreferenceEditor extends PropertyEditorSupport {
70  
71      /** The set of fields used to determine a Durability from a MongoDB URI. */
72      public static final Set<String> MONGODB_URI_FIELDS;
73  
74      /** The logger for the {@link ReadPreferenceEditor}. */
75      protected static final Log LOG = LogFactory
76              .getLog(ReadPreferenceEditor.class);
77  
78      /** Any empty array for tags. */
79      private static final DocumentAssignable[] EMPTY_TAGS = new DocumentAssignable[0];
80  
81      static {
82          final Set<String> fields = new HashSet<String>();
83          fields.add("readpreferencetags");
84          fields.add("readpreference");
85          fields.add("slaveok");
86  
87          MONGODB_URI_FIELDS = Collections.unmodifiableSet(fields);
88      }
89  
90      /**
91       * Creates a new ReadPreferenceEditor.
92       */
93      public ReadPreferenceEditor() {
94          super();
95      }
96  
97      /**
98       * {@inheritDoc}
99       * <p>
100      * Overridden to parse a string to a {@link ReadPreference}.
101      * </p>
102      * 
103      * @throws IllegalArgumentException
104      *             If the string cannot be parsed into a {@link ReadPreference}.
105      */
106     @Override
107     public void setAsText(final String readPreferenceString)
108             throws IllegalArgumentException {
109         if ("PRIMARY".equalsIgnoreCase(readPreferenceString)) {
110             setValue(ReadPreference.primary());
111         }
112         else if ("SECONDARY".equalsIgnoreCase(readPreferenceString)) {
113             setValue(ReadPreference.secondary());
114         }
115         else if ("CLOSEST".equalsIgnoreCase(readPreferenceString)) {
116             setValue(ReadPreference.closest());
117         }
118         else if ("NEAREST".equalsIgnoreCase(readPreferenceString)) {
119             setValue(ReadPreference.closest());
120         }
121         else if ("PREFER_PRIMARY".equalsIgnoreCase(readPreferenceString)) {
122             setValue(ReadPreference.preferPrimary());
123         }
124         else if ("PREFER_SECONDARY".equalsIgnoreCase(readPreferenceString)) {
125             setValue(ReadPreference.preferSecondary());
126         }
127         else if (MongoDbUri.isUri(readPreferenceString)) {
128             final MongoDbUri uri = new MongoDbUri(readPreferenceString);
129             final ReadPreference parsed = fromUriParameters(uri);
130             if (parsed != null) {
131                 setValue(parsed);
132             }
133         }
134         else {
135             // JSON Document?
136             ReadPreference prefs;
137             try {
138                 prefs = null;
139 
140                 final Document doc = Json.parse(readPreferenceString);
141                 final List<DocumentElement> tagDocs = doc.find(
142                         DocumentElement.class, "tags", ".*");
143                 final Document[] tagArray = new Document[tagDocs.size()];
144                 for (int i = 0; i < tagArray.length; ++i) {
145                     tagArray[i] = new RootDocument(tagDocs.get(i).getElements());
146                 }
147 
148                 final Element modeElement = doc.get("mode");
149                 if (modeElement != null) {
150                     final String token = modeElement.getValueAsString();
151                     final ReadPreference.Mode mode = ReadPreference.Mode
152                             .valueOf(token);
153 
154                     switch (mode) {
155                     case NEAREST: {
156                         prefs = ReadPreference.closest(tagArray);
157                         break;
158                     }
159                     case PRIMARY_ONLY: {
160                         prefs = ReadPreference.primary();
161                         break;
162                     }
163                     case PRIMARY_PREFERRED: {
164                         prefs = ReadPreference.preferPrimary(tagArray);
165                         break;
166                     }
167                     case SECONDARY_ONLY: {
168                         prefs = ReadPreference.secondary(tagArray);
169                         break;
170                     }
171                     case SECONDARY_PREFERRED: {
172                         prefs = ReadPreference.preferSecondary(tagArray);
173                         break;
174                     }
175                     case SERVER: {
176                         final Element serverElement = doc.get("server");
177                         if (serverElement != null) {
178                             prefs = ReadPreference.server(serverElement
179                                     .getValueAsString());
180                         }
181                         break;
182                     }
183                     }
184                 }
185             }
186             catch (final JsonException parseError) {
187                 // Fall through.
188                 prefs = null;
189             }
190             catch (final IllegalArgumentException invalidMode) {
191                 // Fall through.
192                 prefs = null;
193             }
194 
195             if (prefs != null) {
196                 setValue(prefs);
197             }
198             else {
199                 throw new IllegalArgumentException(
200                         "Could not determine the read preferences for '"
201                                 + readPreferenceString + "'.");
202             }
203         }
204     }
205 
206     /**
207      * Uses the URI parameters to determine a {@link ReadPreference}. May return
208      * null if the URI did not contain any read preference settings.
209      * 
210      * @param uri
211      *            The URI.
212      * @return The {@link ReadPreference} from the URI parameters.
213      */
214     private ReadPreference fromUriParameters(final MongoDbUri uri) {
215 
216         final Map<String, String> parameters = uri.getParsedOptions();
217 
218         ReadPreference result = null;
219 
220         String value = parameters.remove("slaveok");
221         if (value != null) {
222             if (Boolean.parseBoolean(value)) {
223                 result = ReadPreference.SECONDARY;
224             }
225             else {
226                 result = ReadPreference.PRIMARY;
227             }
228         }
229 
230         value = parameters.remove("readpreference");
231         final List<String> tagsValue = uri.getValuesFor("readpreferencetags");
232         if (value != null) {
233             if ("primary".equalsIgnoreCase(value)) {
234                 result = ReadPreference.PRIMARY;
235             }
236             else if ("primaryPreferred".equalsIgnoreCase(value)) {
237                 result = ReadPreference.preferPrimary(parseTags(tagsValue));
238             }
239             else if ("secondary".equalsIgnoreCase(value)) {
240                 result = ReadPreference.secondary(parseTags(tagsValue));
241             }
242             else if ("secondaryPreferred".equalsIgnoreCase(value)) {
243                 result = ReadPreference.preferSecondary(parseTags(tagsValue));
244             }
245             else if ("nearest".equalsIgnoreCase(value)) {
246                 result = ReadPreference.closest(parseTags(tagsValue));
247             }
248             else {
249                 LOG.warn("Unknown readPreference: '{}'. "
250                         + "Defaulting to primary.", value);
251                 result = ReadPreference.PRIMARY;
252             }
253         }
254 
255         return result;
256     }
257 
258     /**
259      * Parses out the tags documents.
260      * 
261      * @param tagsValue
262      *            The list of tags entries.
263      * @return The tags documents.
264      */
265     private DocumentAssignable[] parseTags(final List<String> tagsValue) {
266         if ((tagsValue == null) || tagsValue.isEmpty()) {
267             return EMPTY_TAGS;
268         }
269 
270         final List<DocumentAssignable> docs = new ArrayList<DocumentAssignable>(
271                 tagsValue.size());
272         for (final String tagValue : tagsValue) {
273             docs.add(Json.parse("{" + tagValue + "}"));
274         }
275         return docs.toArray(EMPTY_TAGS);
276     }
277 }