View Javadoc
1   /*
2    * #%L
3    * RootDocument.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.bson.impl;
21  
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import com.allanbank.mongodb.bson.Document;
33  import com.allanbank.mongodb.bson.Element;
34  import com.allanbank.mongodb.bson.element.ObjectId;
35  import com.allanbank.mongodb.bson.element.ObjectIdElement;
36  
37  /**
38   * A root level document that can inject a _id value.
39   * 
40   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
41   *         mutated in incompatible ways between any two releases of the driver.
42   * @copyright 2011-2014, Allanbank Consulting, Inc., All Rights Reserved
43   */
44  public class RootDocument extends AbstractDocument {
45  
46      /** Serialization version for the class. */
47      private static final long serialVersionUID = -2875918328146027036L;
48  
49      /**
50       * Computes and returns the number of bytes that are used to encode the
51       * document.
52       * 
53       * @param entries
54       *            The entries in the document.
55       * @return The size of the document when encoded in bytes.
56       */
57      private static long computeSize(final List<Element> entries) {
58          long result = 5; // int length (4) + terminal null byte (1).
59          if ((entries != null) && !entries.isEmpty()) {
60              for (final Element element : entries) {
61                  result += element.size();
62              }
63          }
64  
65          return result;
66      }
67  
68      /** The elements of the document. */
69      final AtomicReference<List<Element>> myElements;
70  
71      /**
72       * Tracks if the _id field is known to exist in the document when
73       * constructed.
74       */
75      final boolean myIdKnownPresent;
76  
77      /**
78       * Constructed when a user tries to access the elements of the document by
79       * name.
80       */
81      private final AtomicReference<Map<String, Element>> myElementMap;
82  
83      /** The size of the document when encoded as bytes. */
84      private transient long mySize;
85  
86      /**
87       * Constructs a new {@link RootDocument}.
88       * 
89       * @param elements
90       *            The elements for the BSON document.
91       */
92      public RootDocument(final Element... elements) {
93          this(Arrays.asList(elements), false);
94      }
95  
96      /**
97       * Constructs a new {@link RootDocument}.
98       * 
99       * @param elements
100      *            The elements for the BSON document.
101      */
102     public RootDocument(final List<Element> elements) {
103         this(elements, false);
104     }
105 
106     /**
107      * Constructs a new {@link RootDocument}.
108      * 
109      * @param elements
110      *            The elements for the BSON document.
111      * @param idPresent
112      *            If true then there is an _id element in the list of elements.
113      */
114     public RootDocument(final List<Element> elements, final boolean idPresent) {
115         this(elements, idPresent, computeSize(elements));
116     }
117 
118     /**
119      * Constructs a new {@link RootDocument}.
120      * 
121      * @param elements
122      *            The elements for the BSON document.
123      * @param idPresent
124      *            If true then there is an _id element in the list of elements.
125      * @param size
126      *            The size of the document when encoded in bytes. If not known
127      *            then use the {@link RootDocument#RootDocument(List, boolean)}
128      *            constructor instead.
129      */
130     public RootDocument(final List<Element> elements, final boolean idPresent,
131             final long size) {
132         myElements = new AtomicReference<List<Element>>();
133         myElementMap = new AtomicReference<Map<String, Element>>();
134         if ((elements != null) && !elements.isEmpty()) {
135             myElements.set(Collections.unmodifiableList(new ArrayList<Element>(
136                     elements)));
137         }
138         else {
139             myElements.set(EMPTY_ELEMENTS);
140         }
141         myIdKnownPresent = idPresent;
142         mySize = size;
143     }
144 
145     /**
146      * Returns true if the document contains an element with the specified name.
147      * 
148      * @see Document#contains(String)
149      */
150     @Override
151     public boolean contains(final String name) {
152         return (myIdKnownPresent && "_id".equals(name)) || super.contains(name);
153     }
154 
155     /**
156      * Returns the elements in the document.
157      * 
158      * @return The elements in the document.
159      */
160     @Override
161     public List<Element> getElements() {
162         return myElements.get();
163     }
164 
165     /**
166      * Adds an {@link ObjectIdElement} to the head of the document.
167      */
168     public void injectId() {
169         if (!contains("_id")) {
170             final List<Element> old = myElements.get();
171 
172             final ObjectIdElement toAdd = new ObjectIdElement("_id",
173                     new ObjectId());
174 
175             final List<Element> newElements = new ArrayList<Element>(
176                     old.size() + 1);
177             newElements.add(toAdd);
178             newElements.addAll(old);
179 
180             if (myElements.compareAndSet(old, newElements)) {
181                 myElementMap.set(null);
182                 mySize += toAdd.size();
183             }
184         }
185     }
186 
187     /**
188      * Returns the size of the document when encoded as bytes.
189      * 
190      * @return The size of the document when encoded as bytes.
191      */
192     @Override
193     public long size() {
194         return mySize;
195     }
196 
197     /**
198      * Returns a map from the element names to the elements in the document.
199      * Used for faster by-name access.
200      * 
201      * @return The element name to element mapping.
202      */
203     @Override
204     protected Map<String, Element> getElementMap() {
205         if (myElementMap.get() == null) {
206             final List<Element> elements = myElements.get();
207             final Map<String, Element> mapping = new HashMap<String, Element>(
208                     elements.size() + elements.size());
209 
210             for (final Element element : elements) {
211                 mapping.put(element.getName(), element);
212             }
213 
214             // Swap the finished map into position.
215             myElementMap.compareAndSet(null, mapping);
216         }
217 
218         return myElementMap.get();
219     }
220 
221     /**
222      * Sets the transient state of this document.
223      * 
224      * @param in
225      *            The input stream.
226      * @throws ClassNotFoundException
227      *             On a failure loading a class in this classed reachable tree.
228      * @throws IOException
229      *             On a failure reading from the stream.
230      */
231     private void readObject(final ObjectInputStream in)
232             throws ClassNotFoundException, IOException {
233         in.defaultReadObject();
234         mySize = computeSize(getElements());
235     }
236 }