Java tutorial
/* Copyright 2013-2015 Immutables Authors and Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package org.immutables.mongo.repository.internal; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedBytes; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonWriter; import com.mongodb.DBCallback; import com.mongodb.DBCollection; import com.mongodb.DBDecoder; import com.mongodb.DBDecoderFactory; import com.mongodb.DBEncoder; import com.mongodb.DBObject; import com.mongodb.DefaultDBEncoder; import com.mongodb.LazyDBCallback; import de.undercouch.bson4jackson.BsonFactory; import de.undercouch.bson4jackson.BsonGenerator; import de.undercouch.bson4jackson.BsonParser; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.bson.BSONCallback; import org.bson.BSONObject; import org.bson.BasicBSONDecoder; import org.bson.io.BasicOutputBuffer; import org.bson.io.OutputBuffer; /** * MongoDB driver specific encoding and jumping hoops. */ @SuppressWarnings("resource") public final class BsonEncoding { private static final BsonFactory BSON_FACTORY = new BsonFactory() .enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH); private static final JsonFactory JSON_FACTORY = new JsonFactory().enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES) .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES); /** * This field name will cause an MongoDB confuse if not unwrapped correctly so it's may be a good * choice. */ private static final String PREENCODED_VALUE_WRAPPER_FIELD_NAME = "$"; private BsonEncoding() { } /** * Although it may seem that re-parsing is bizarre, but it is one [of not so many] ways to do * proper marshaling. This kind of inefficiency will only hit query constraints that have many * object with custom marshaling, which considered to be a rare case. * @param adapted adapted value that know how to write itself to {@link JsonWriter} * @return object converted to MongoDB driver's {@link BSONObject}. */ public static Object unwrapBsonable(Support.Adapted<?> adapted) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); BsonGenerator generator = BSON_FACTORY.createGenerator(outputStream); BsonWriter writer = new BsonWriter(generator); writer.beginObject().name(PREENCODED_VALUE_WRAPPER_FIELD_NAME); adapted.write(writer); writer.endObject(); writer.close(); BSONObject object = new BasicBSONDecoder().readObject(outputStream.toByteArray()); return object.get(PREENCODED_VALUE_WRAPPER_FIELD_NAME); } catch (IOException ex) { throw Throwables.propagate(ex); } } public static DBObject unwrapJsonable(String json) { try { JsonParser parser = JSON_FACTORY.createParser(json); parser.nextToken(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); BsonGenerator generator = BSON_FACTORY.createGenerator(outputStream); generator.copyCurrentStructure(parser); generator.close(); parser.close(); byte[] data = outputStream.toByteArray(); return (DBObject) new LazyDBCallback(null).createObject(data, 0); } catch (IOException ex) { throw Throwables.propagate(ex); } } public static <T> T unmarshalDbObject(DBObject dbObject, TypeAdapter<T> adaper) throws IOException { BasicOutputBuffer buffer = new BasicOutputBuffer(); encoder().writeObject(buffer, dbObject); BsonParser parser = BSON_FACTORY.createParser(buffer.toByteArray()); BsonReader reader = new BsonReader(parser); T instance = adaper.read(reader); reader.close(); return instance; } private static class CountingOutputBufferStream extends OutputStream { final OutputBuffer buffer; int count; CountingOutputBufferStream(OutputBuffer buffer) { this.buffer = buffer; } @Override public void write(byte[] bytes, int offset, int length) throws IOException { buffer.write(bytes, offset, length); count += length; } @Override public void write(int byteValue) throws IOException { buffer.write(byteValue); count++; } } public static DBEncoder encoder() { return Encoder.ENCODER; } enum Encoder implements DBEncoder { ENCODER; @Override public int writeObject(OutputBuffer buffer, BSONObject object) { try { if (object instanceof WritableObjectPosition) { return ((WritableObjectPosition) object).writePlainCurrent(buffer); } return DefaultDBEncoder.FACTORY.create().writeObject(buffer, object); } catch (IOException ex) { throw Throwables.propagate(ex); } } } public static <T> DBObject wrapUpdateObject(T instance, TypeAdapter<T> adaper) { return new UpdateObject<>(instance, adaper); } public static <T> List<DBObject> wrapInsertObjectList(ImmutableList<T> list, TypeAdapter<T> adaper) { return new InsertObjectList<>(list, adaper); } interface WritableObjectPosition { int writeCurrent(OutputBuffer buffer) throws IOException; int writePlainCurrent(OutputBuffer buffer) throws IOException; } private static class UpdateObject<T> implements DBObject, WritableObjectPosition { private final T instance; private final TypeAdapter<T> adaper; UpdateObject(T instance, TypeAdapter<T> adaper) { this.instance = instance; this.adaper = adaper; } @Override public int writeCurrent(OutputBuffer buffer) throws IOException { CountingOutputBufferStream outputStream = new CountingOutputBufferStream(buffer); BsonWriter writer = new BsonWriter(BSON_FACTORY.createGenerator(outputStream)); adaper.write(writer, instance); writer.close(); return outputStream.count; } @Override public int writePlainCurrent(OutputBuffer buffer) throws IOException { return writeCurrent(buffer); } @Override public Object put(String key, Object v) { throw new UnsupportedOperationException(); } @Override public void putAll(BSONObject o) { throw new UnsupportedOperationException(); } @Override public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override public Object get(String key) { throw new UnsupportedOperationException(); } @Override public Map toMap() { throw new UnsupportedOperationException(); } @Override public Object removeField(String key) { throw new UnsupportedOperationException(); } @Deprecated @Override public boolean containsKey(String s) { throw new UnsupportedOperationException(); } @Override public boolean containsField(String s) { throw new UnsupportedOperationException(); } @Override public Set<String> keySet() { return ImmutableSet.of(); } @Override public void markAsPartialObject() { } @Override public boolean isPartialObject() { return false; } } private static class InsertObjectList<T> implements DBObject, List<DBObject>, WritableObjectPosition { private final ImmutableList<T> list; private int position; @Nullable private JsonWriter writer; private CountingOutputBufferStream outputStream; private final TypeAdapter<T> adaper; InsertObjectList(ImmutableList<T> list, TypeAdapter<T> adaper) { this.list = list; this.adaper = adaper; } @Override public int writeCurrent(OutputBuffer buffer) throws IOException { createGeneratorIfNecessary(buffer); int previousByteCount = outputStream.count; adaper.write(writer, list.get(position)); if (isLastPosition()) { closeWriter(); } return outputStream.count - previousByteCount; } @Override public int writePlainCurrent(OutputBuffer buffer) throws IOException { CountingOutputBufferStream outputStream = new CountingOutputBufferStream(buffer); BsonWriter writer = new BsonWriter(BSON_FACTORY.createGenerator(outputStream)); adaper.write(writer, list.get(position)); writer.close(); return outputStream.count; } private void closeWriter() throws IOException { if (writer != null) { writer.close(); writer = null; } } private void createGeneratorIfNecessary(OutputBuffer buffer) throws IOException { if (writer == null) { outputStream = new CountingOutputBufferStream(buffer); writer = new BsonWriter(BSON_FACTORY.createGenerator(outputStream)); } } private boolean isLastPosition() { return position == list.size() - 1; } @Override public DBObject get(int index) { position = index; return this; } @Override public int size() { return list.size(); } @Override public Object put(String key, Object v) { throw new UnsupportedOperationException(); } @Override public void putAll(BSONObject o) { throw new UnsupportedOperationException(); } @Override public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override public Object get(String key) { throw new UnsupportedOperationException(); } @Override public Map toMap() { throw new UnsupportedOperationException(); } @Override public Object removeField(String key) { throw new UnsupportedOperationException(); } @Deprecated @Override public boolean containsKey(String s) { throw new UnsupportedOperationException(); } @Override public boolean containsField(String s) { throw new UnsupportedOperationException(); } @Override public Set<String> keySet() { throw new UnsupportedOperationException(); } @Override public void markAsPartialObject() { throw new UnsupportedOperationException(); } @Override public boolean isPartialObject() { return false; } @Override public boolean add(DBObject e) { throw new UnsupportedOperationException(); } @Override public void add(int index, DBObject element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends DBObject> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection<? extends DBObject> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public int indexOf(Object o) { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return false; } @Override public Iterator<DBObject> iterator() { return ImmutableSet.<DBObject>of().iterator(); } @Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); } @Override public ListIterator<DBObject> listIterator() { return ImmutableList.<DBObject>of().listIterator(); } @Override public ListIterator<DBObject> listIterator(int index) { return ImmutableList.<DBObject>of().listIterator(); } @Override public DBObject set(int index, DBObject element) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public DBObject remove(int index) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public List<DBObject> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <V> V[] toArray(V[] a) { throw new UnsupportedOperationException(); } } public static <T> ImmutableList<T> unwrapResultObjectList(List<DBObject> result) { if (result.isEmpty()) { return ImmutableList.of(); } // Safe as long as caller will use same T for decoder and unwrap @SuppressWarnings("unchecked") List<T> results = ((ResultDecoder<T>) result.get(0)).results; return ImmutableList.copyOf(results); } public static <T> DBDecoderFactory newResultDecoderFor(TypeAdapter<T> adaper, int expectedSize) { return new ResultDecoder<>(adaper, expectedSize); } /** * Special input stream that operates from as writable byte buffer that is filled with BSON object * from other input stream ({@link #resetObjectFrom(InputStream)}). * Extending buffered input stream * to prevent excessive wraping in another buffered stream by {@link BsonParser} */ private static final class ObjectBufferInputStream extends BufferedInputStream { private byte[] buffer; private int position; private int limit; ObjectBufferInputStream(int capacity) { super(null, 1); ensureBufferWithCapacity(capacity); } private void ensureBufferWithCapacity(int capacity) { if (buffer == null || buffer.length < capacity) { Preconditions.checkArgument(capacity >= 4); byte[] temp = buffer; this.buffer = new byte[capacity]; if (temp != null) { System.arraycopy(temp, 0, buffer, 0, temp.length); } } } void resetObjectFrom(InputStream inputStream) throws IOException { ByteStreams.readFully(inputStream, buffer, 0, Ints.BYTES); int objectSize = Ints.fromBytes(buffer[3], buffer[2], buffer[1], buffer[0]); ensureBufferWithCapacity(objectSize); ByteStreams.readFully(inputStream, buffer, Ints.BYTES, objectSize - Ints.BYTES); position = 0; limit = objectSize; } @Override public int available() throws IOException { return limit - position; } @Override public int read(byte[] b, int off, int len) throws IOException { len = Math.min(len, available()); System.arraycopy(buffer, position, b, off, len); position += len; return len; } @Override public int read() throws IOException { if (available() > 0) { return UnsignedBytes.toInt(buffer[position++]); } return -1; } } private static final class ResultDecoder<T> implements DBDecoderFactory, DBDecoder, DBObject { final List<T> results; private final TypeAdapter<T> adaper; @Nullable private BsonReader parser; private final ObjectBufferInputStream bufferStream = new ObjectBufferInputStream(2012); ResultDecoder(TypeAdapter<T> adaper, int expectedSize) { this.adaper = adaper; this.results = Lists.newArrayListWithExpectedSize(expectedSize); } private BsonReader createParserIfNecessary() throws IOException { if (parser != null) { parser.close(); } parser = new BsonReader(BSON_FACTORY.createParser(bufferStream)); return parser; } @Override public DBObject decode(InputStream inputStream, DBCollection collection) throws IOException { bufferStream.resetObjectFrom(inputStream); createParserIfNecessary(); T object = adaper.read(parser); results.add(object); return this; } @Override public DBDecoder create() { return this; } @Override public BSONObject readObject(byte[] b) { throw new UnsupportedOperationException(); } @Override public BSONObject readObject(InputStream in) throws IOException { throw new UnsupportedOperationException(); } @Override public int decode(byte[] b, BSONCallback callback) { throw new UnsupportedOperationException(); } @Override public int decode(InputStream in, BSONCallback callback) throws IOException { throw new UnsupportedOperationException(); } @Override public DBCallback getDBCallback(DBCollection collection) { throw new UnsupportedOperationException(); } @Override public DBObject decode(byte[] b, DBCollection collection) { throw new UnsupportedOperationException(); } @Override public Object put(String key, Object v) { throw new UnsupportedOperationException(); } @Override public void putAll(BSONObject o) { throw new UnsupportedOperationException(); } @Override public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override public Object get(String key) { return null; } @Override public Map toMap() { throw new UnsupportedOperationException(); } @Override public Object removeField(String key) { throw new UnsupportedOperationException(); } @Deprecated @Override public boolean containsKey(String s) { throw new UnsupportedOperationException(); } @Override public boolean containsField(String s) { throw new UnsupportedOperationException(); } @Override public Set<String> keySet() { throw new UnsupportedOperationException(); } @Override public void markAsPartialObject() { } @Override public boolean isPartialObject() { return false; } } }