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 }