/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.async.client;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientException;
import com.mongodb.MongoDriverInformation;
import com.mongodb.MongoInternalException;
import com.mongodb.connection.AsynchronousSocketChannelStreamFactoryFactory;
import com.mongodb.connection.Cluster;
import com.mongodb.connection.DefaultClusterFactory;
import com.mongodb.connection.StreamFactory;
import com.mongodb.connection.StreamFactoryFactory;
import com.mongodb.connection.TlsChannelStreamFactoryFactory;
import com.mongodb.lang.Nullable;
import org.bson.codecs.configuration.CodecRegistry;

import java.io.Closeable;

import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.event.EventListenerHelper.getCommandListener;

/**
 * A factory for MongoClient instances.
 *
 * @since 3.0
 * @deprecated Prefer the Reactive Streams-based asynchronous driver (mongodb-driver-reactivestreams artifactId)
 */
@Deprecated
public final class MongoClients {

    /**
     * Creates a new client with the default connection string "mongodb://localhost".
     *
     * @return the client
     */
    public static MongoClient create() {
        return create(new ConnectionString("mongodb://localhost"));
    }

    /**
     * Create a new client with the given client settings.
     *
     * @param settings the settings
     * @return the client
     * @deprecated use {@link #create(com.mongodb.MongoClientSettings)} instead
     */
    @Deprecated
    public static MongoClient create(final MongoClientSettings settings) {
        return create(settings, null);
    }

    /**
     * Create a new client with the given connection string as if by a call to {@link #create(ConnectionString)}.
     *
     * @param connectionString the connection
     * @return the client
     * @see #create(ConnectionString)
     */
    public static MongoClient create(final String connectionString) {
        return create(new ConnectionString(connectionString));
    }

    /**
     * Create a new client with the given connection string.
     *

     * For each of the settings classed configurable via {@link com.mongodb.MongoClientSettings}, the connection string is applied by
     * calling the {@code applyConnectionString} method on an instance of setting's builder class, building the setting, and adding it to
     * an instance of {@link com.mongodb.MongoClientSettings.Builder}.
     *

     *

     * The connection string's stream type is then applied by setting the
     * {@link com.mongodb.connection.StreamFactory} to an instance of NettyStreamFactory,
     *

     *
     * @param connectionString the settings
     * @return the client
     * @throws IllegalArgumentException if the connection string's stream type is not one of "netty" or "nio2"
     *
     * @see ConnectionString#getStreamType()
     * @see com.mongodb.MongoClientSettings.Builder
     * @see com.mongodb.connection.ClusterSettings.Builder#applyConnectionString(ConnectionString)
     * @see com.mongodb.connection.ConnectionPoolSettings.Builder#applyConnectionString(ConnectionString)
     * @see com.mongodb.connection.ServerSettings.Builder#applyConnectionString(ConnectionString)
     * @see com.mongodb.connection.SslSettings.Builder#applyConnectionString(ConnectionString)
     * @see com.mongodb.connection.SocketSettings.Builder#applyConnectionString(ConnectionString)
     */
    public static MongoClient create(final ConnectionString connectionString) {
        return create(connectionString, null);
    }

    /**
     * Creates a new client with the given client settings.
     *
     *
Note: Intended for driver and library authors to associate extra driver metadata with the connections.

     *
     * @param settings               the settings
     * @param mongoDriverInformation any driver information to associate with the MongoClient
     * @return the client
     * @since 3.4
     * @deprecated use {@link #create(com.mongodb.MongoClientSettings, MongoDriverInformation)} instead
     */
    @Deprecated
    public static MongoClient create(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation) {
        return create(settings, mongoDriverInformation, null);
    }

    /**
     * Create a new client with the given connection string.
     *
     *
Note: Intended for driver and library authors to associate extra driver metadata with the connections.

     *
     * @param connectionString       the settings
     * @param mongoDriverInformation any driver information to associate with the MongoClient
     * @return the client
     * @throws IllegalArgumentException if the connection string's stream type is not one of "netty" or "nio2"
     * @see MongoClients#create(ConnectionString)
     */
    public static MongoClient create(final ConnectionString connectionString,
                                     @Nullable final MongoDriverInformation mongoDriverInformation) {

        return create(MongoClientSettings.builder().applyConnectionString(connectionString).build(),
                mongoDriverInformation, connectionString.getStreamType());
    }

    /**
     * Create a new client with the given client settings.
     *
     * @param settings the settings
     * @return the client
     * @since 3.7
     */
    public static MongoClient create(final com.mongodb.MongoClientSettings settings) {
        return create(settings, null);
    }

    /**
     * Creates a new client with the given client settings.
     *
     *
Note: Intended for driver and library authors to associate extra driver metadata with the connections.

     *
     * @param settings               the settings
     * @param mongoDriverInformation any driver information to associate with the MongoClient
     * @return the client
     * @since 3.7
     */
    public static MongoClient create(final com.mongodb.MongoClientSettings settings,
                                     @Nullable final MongoDriverInformation mongoDriverInformation) {
        return create(MongoClientSettings.createFromClientSettings(settings), mongoDriverInformation, null);
    }

    private static MongoClient create(final MongoClientSettings settings,
                                      @Nullable final MongoDriverInformation mongoDriverInformation,
                                      @Nullable final String requestedStreamType) {
        String streamType = getStreamType(requestedStreamType);
        if (settings.getStreamFactoryFactory() == null) {
           if (isNetty(streamType)) {
               return NettyMongoClients.create(settings, mongoDriverInformation);
           } else if (isNio(streamType)) {
               if (settings.getSslSettings().isEnabled()) {
                   return createWithTlsChannel(settings, mongoDriverInformation);
               } else {
                   return createWithAsynchronousSocketChannel(settings, mongoDriverInformation);
               }
           } else {
               throw new IllegalArgumentException("Unsupported stream type: " + streamType);
           }
        } else {
            return createMongoClient(settings, mongoDriverInformation, getStreamFactory(settings, false),
                    getStreamFactory(settings, true), null);
        }
    }

    static MongoClient createMongoClient(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation,
                                         final StreamFactory streamFactory, final StreamFactory heartbeatStreamFactory,
                                         @Nullable final Closeable externalResourceCloser) {
        return new MongoClientImpl(settings, createCluster(settings, mongoDriverInformation, streamFactory, heartbeatStreamFactory),
                externalResourceCloser);
    }

    private static Cluster createCluster(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation,
                                         final StreamFactory streamFactory, final StreamFactory heartbeatStreamFactory) {
        notNull("settings", settings);
        MongoDriverInformation.Builder builder = mongoDriverInformation == null ? MongoDriverInformation.builder()
                : MongoDriverInformation.builder(mongoDriverInformation);
        return new DefaultClusterFactory().createCluster(settings.getClusterSettings(), settings.getServerSettings(),
                settings.getConnectionPoolSettings(), streamFactory, heartbeatStreamFactory, settings.getCredentialList(),
                getCommandListener(settings.getCommandListeners()), settings.getApplicationName(), builder.driverName("async").build(),
                settings.getCompressorList());
    }

    /**
     * Gets the default codec registry.  It includes the following providers:
     *
     *

     *
{@link org.bson.codecs.ValueCodecProvider}

     *
{@link org.bson.codecs.BsonValueCodecProvider}

     *
{@link com.mongodb.DBRefCodecProvider}

     *
{@link com.mongodb.DBObjectCodecProvider}

     *
{@link org.bson.codecs.DocumentCodecProvider}

     *
{@link org.bson.codecs.IterableCodecProvider}

     *
{@link org.bson.codecs.MapCodecProvider}

     *
{@link com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider}

     *
{@link com.mongodb.client.gridfs.codecs.GridFSFileCodecProvider}

     *
{@link org.bson.codecs.jsr310.Jsr310CodecProvider}

     *
{@link org.bson.codecs.BsonCodecProvider}

     *

     *
     * @return the default codec registry
     * @see com.mongodb.MongoClientSettings#getCodecRegistry()
     * @since 3.1
     */
    public static CodecRegistry getDefaultCodecRegistry() {
        return com.mongodb.MongoClientSettings.getDefaultCodecRegistry();
    }

    private static MongoClient createWithTlsChannel(final MongoClientSettings settings,
                                                    @Nullable final MongoDriverInformation mongoDriverInformation) {
        if (!isJava8()) {
            throw new MongoClientException("TLS is only supported natively with Java 8 and above. Please use Netty instead");
        }
        final TlsChannelStreamFactoryFactory streamFactoryFactory = new TlsChannelStreamFactoryFactory();
        StreamFactory streamFactory = streamFactoryFactory.create(settings.getSocketSettings(), settings.getSslSettings());
        StreamFactory heartbeatStreamFactory = streamFactoryFactory.create(settings.getHeartbeatSocketSettings(),
                settings.getSslSettings());
        return createMongoClient(settings, mongoDriverInformation, streamFactory, heartbeatStreamFactory,
                new Closeable() {
                    @Override
                    public void close() {
                        streamFactoryFactory.close();
                    }
                });
    }

    private static boolean isJava8() {
        try {
            Class.forName("java.time.Instant");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private static MongoClient createWithAsynchronousSocketChannel(final MongoClientSettings settings,
                                                                   @Nullable final MongoDriverInformation mongoDriverInformation) {
        StreamFactoryFactory streamFactoryFactory = new AsynchronousSocketChannelStreamFactoryFactory();
        StreamFactory streamFactory = streamFactoryFactory.create(settings.getSocketSettings(), settings.getSslSettings());
        StreamFactory heartbeatStreamFactory = streamFactoryFactory.create(settings.getHeartbeatSocketSettings(),
                settings.getSslSettings());
        return createMongoClient(settings, mongoDriverInformation, streamFactory, heartbeatStreamFactory, null);
    }

    private static StreamFactory getStreamFactory(final MongoClientSettings settings, final boolean isHeartbeat) {
        StreamFactoryFactory streamFactoryFactory = settings.getStreamFactoryFactory();
        if (streamFactoryFactory == null) {
            throw new MongoInternalException("should not happen");
        }
        return streamFactoryFactory.create(isHeartbeat ? settings.getHeartbeatSocketSettings() : settings.getSocketSettings(),
                settings.getSslSettings());
    }

    private static boolean isNetty(final String streamType) {
        return streamType.toLowerCase().equals("netty");
    }

    private static boolean isNio(final String streamType) {
        return streamType.toLowerCase().equals("nio2");
    }

    private static String getStreamType(@Nullable final String requestedStreamType) {
        if (requestedStreamType != null) {
            return requestedStreamType;
        } else {
            return System.getProperty("org.mongodb.async.type", "nio2");
        }
    }

    private MongoClients() {
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.
 */

// BasicDBList.java

package com.mongodb;

import org.bson.types.BasicBSONList;

/**
 * An implementation of List that reflects the way BSON lists work.
 */
public class BasicDBList extends BasicBSONList implements DBObject {

    private static final long serialVersionUID = -4415279469780082174L;

    @Override
    public boolean isPartialObject() {
        return _isPartialObject;
    }

    @Override
    public void markAsPartialObject() {
        _isPartialObject = true;
    }

    /**
     * Copies this instance into a new Object.
     *
     * @return a new BasicDBList with the same values as this instance
     */
    public Object copy() {
        // copy field values into new object
        BasicDBList newobj = new BasicDBList();
        // need to clone the sub obj
        for (int i = 0; i < size(); ++i) {
            Object val = get(i);
            if (val instanceof BasicDBObject) {
                val = ((BasicDBObject) val).copy();
            } else if (val instanceof BasicDBList) {
                val = ((BasicDBList) val).copy();
            }
            newobj.add(val);
        }
        return newobj;
    }

    private boolean _isPartialObject;
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.bson.codecs.pojo;

import java.util.List;
import java.util.Map;

/**
 * This model represents the metadata for a class and all its properties.
 *
 * @param <T> The type of the class the ClassModel represents
 * @since 3.5
 */
public final class ClassModel<T> {
    private final String name;
    private final Class<T> type;
    private final boolean hasTypeParameters;
    private final InstanceCreatorFactory<T> instanceCreatorFactory;
    private final boolean discriminatorEnabled;
    private final String discriminatorKey;
    private final String discriminator;
    private final IdPropertyModelHolder<?> idPropertyModelHolder;
    private final List<PropertyModel<?>> propertyModels;
    private final Map<String, TypeParameterMap> propertyNameToTypeParameterMap;

    ClassModel(final Class<T> clazz, final Map<String, TypeParameterMap> propertyNameToTypeParameterMap,
               final InstanceCreatorFactory<T> instanceCreatorFactory, final Boolean discriminatorEnabled, final String discriminatorKey,
               final String discriminator, final IdPropertyModelHolder<?> idPropertyModelHolder,
               final List<PropertyModel<?>> propertyModels) {
        this.name = clazz.getSimpleName();
        this.type = clazz;
        this.hasTypeParameters = clazz.getTypeParameters().length > 0;
        this.propertyNameToTypeParameterMap = propertyNameToTypeParameterMap;
        this.instanceCreatorFactory = instanceCreatorFactory;
        this.discriminatorEnabled = discriminatorEnabled;
        this.discriminatorKey = discriminatorKey;
        this.discriminator = discriminator;
        this.idPropertyModelHolder = idPropertyModelHolder;
        this.propertyModels = propertyModels;
    }

    /**
     * Creates a new Class Model builder instance using reflection.
     *
     * @param type the POJO class to reflect and configure the builder with.
     * @param <S> the type of the class
     * @return a new Class Model builder instance using reflection on the {@code clazz}.
     */
    public static <S> ClassModelBuilder<S> builder(final Class<S> type) {
        return new ClassModelBuilder<S>(type);
    }

    /**
     * @return a new InstanceCreator instance for the ClassModel
     */
    InstanceCreator<T> getInstanceCreator() {
        return instanceCreatorFactory.create();
    }

    /**
     * @return the backing class for the ClassModel
     */
    public Class<T> getType() {
        return type;
    }

    /**
     * @return true if the underlying type has type parameters.
     */
    public boolean hasTypeParameters() {
        return hasTypeParameters;
    }

    /**
     * @return true if a discriminator should be used when storing the data.
     */
    public boolean useDiscriminator() {
        return discriminatorEnabled;
    }

    /**
     * Gets the value for the discriminator.
     *
     * @return the discriminator value or null if not set
     */
    public String getDiscriminatorKey() {
        return discriminatorKey;
    }

    /**
     * Returns the discriminator key.
     *
     * @return the discriminator key or null if not set
     */
    public String getDiscriminator() {
        return discriminator;
    }

    /**
     * Gets a {@link PropertyModel} by the property name.
     *
     * @param propertyName the PropertyModel's property name
     * @return the PropertyModel or null if the property is not found
     */
    public PropertyModel<?> getPropertyModel(final String propertyName) {
        for (PropertyModel<?> propertyModel : propertyModels) {
            if (propertyModel.getName().equals(propertyName)) {
                return propertyModel;
            }
        }
        return null;
    }

    /**
     * Returns all the properties on this model
     *
     * @return the list of properties
     */
    public List<PropertyModel<?>> getPropertyModels() {
        return propertyModels;
    }

    /**
     * Returns the {@link PropertyModel} mapped as the id property for this ClassModel
     *
     * @return the PropertyModel for the id
     */
    public PropertyModel<?> getIdPropertyModel() {
        return idPropertyModelHolder != null ? idPropertyModelHolder.getPropertyModel() : null;
    }

    IdPropertyModelHolder<?> getIdPropertyModelHolder() {
        return idPropertyModelHolder;
    }

    /**
     * Returns the name of the class represented by this ClassModel
     *
     * @return the name
     */
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "ClassModel{"
                + "type=" + type
                + "}";
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ClassModel<?> that = (ClassModel<?>) o;

        if (discriminatorEnabled != that.discriminatorEnabled) {
            return false;
        }
        if (!getType().equals(that.getType())) {
            return false;
        }
        if (!getInstanceCreatorFactory().equals(that.getInstanceCreatorFactory())) {
            return false;
        }
        if (getDiscriminatorKey() != null ? !getDiscriminatorKey().equals(that.getDiscriminatorKey())
                : that.getDiscriminatorKey() != null) {
            return false;
        }
        if (getDiscriminator() != null ? !getDiscriminator().equals(that.getDiscriminator()) : that.getDiscriminator() != null) {
            return false;
        }
        if (idPropertyModelHolder != null ? !idPropertyModelHolder.equals(that.idPropertyModelHolder)
                : that.idPropertyModelHolder != null) {
            return false;
        }
        if (!getPropertyModels().equals(that.getPropertyModels())) {
            return false;
        }
        if (!getPropertyNameToTypeParameterMap().equals(that.getPropertyNameToTypeParameterMap())) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = getType().hashCode();
        result = 31 * result + getInstanceCreatorFactory().hashCode();
        result = 31 * result + (discriminatorEnabled ? 1 : 0);
        result = 31 * result + (getDiscriminatorKey() != null ? getDiscriminatorKey().hashCode() : 0);
        result = 31 * result + (getDiscriminator() != null ? getDiscriminator().hashCode() : 0);
        result = 31 * result + (getIdPropertyModelHolder() != null ? getIdPropertyModelHolder().hashCode() : 0);
        result = 31 * result + getPropertyModels().hashCode();
        result = 31 * result + getPropertyNameToTypeParameterMap().hashCode();
        return result;
    }

    InstanceCreatorFactory<T> getInstanceCreatorFactory() {
        return instanceCreatorFactory;
    }

    Map<String, TypeParameterMap> getPropertyNameToTypeParameterMap() {
        return propertyNameToTypeParameterMap;
    }

}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.client.model;

import com.mongodb.lang.Nullable;
import org.bson.conversions.Bson;

import java.util.concurrent.TimeUnit;

import static com.mongodb.assertions.Assertions.notNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * The options to apply to an operation that atomically finds a document and replaces it.
 *
 * @mongodb.driver.manual reference/command/findAndModify/
 * @since 3.0
 */
public class FindOneAndReplaceOptions {
    private Bson projection;
    private Bson sort;
    private boolean upsert;
    private ReturnDocument returnDocument = ReturnDocument.BEFORE;
    private long maxTimeMS;
    private Boolean bypassDocumentValidation;
    private Collation collation;

    /**
     * Gets a document describing the fields to return for all matching documents.
     *
     * @return the project document, which may be null
     * @mongodb.driver.manual tutorial/project-fields-from-query-results Projection
     */
    @Nullable
    public Bson getProjection() {
        return projection;
    }

    /**
     * Sets a document describing the fields to return for all matching documents.
     *
     * @param projection the project document, which may be null.
     * @return this
     * @mongodb.driver.manual tutorial/project-fields-from-query-results Projection
     */
    public FindOneAndReplaceOptions projection(@Nullable final Bson projection) {
        this.projection = projection;
        return this;
    }

    /**
     * Gets the sort criteria to apply to the query. The default is null, which means that the documents will be returned in an undefined
     * order.
     *
     * @return a document describing the sort criteria
     * @mongodb.driver.manual reference/method/cursor.sort/ Sort
     */
    @Nullable
    public Bson getSort() {
        return sort;
    }

    /**
     * Sets the sort criteria to apply to the query.
     *
     * @param sort the sort criteria, which may be null.
     * @return this
     * @mongodb.driver.manual reference/method/cursor.sort/ Sort
     */
    public FindOneAndReplaceOptions sort(@Nullable final Bson sort) {
        this.sort = sort;
        return this;
    }

    /**
     * Returns true if a new document should be inserted if there are no matches to the query filter.  The default is false.
     *
     * @return true if a new document should be inserted if there are no matches to the query filter
     */
    public boolean isUpsert() {
        return upsert;
    }

    /**
     * Set to true if a new document should be inserted if there are no matches to the query filter.
     *
     * @param upsert true if a new document should be inserted if there are no matches to the query filter
     * @return this
     */
    public FindOneAndReplaceOptions upsert(final boolean upsert) {
        this.upsert = upsert;
        return this;
    }

    /**
     * Gets the {@link ReturnDocument} value indicating whether to return the document before it was replaced or after
     *
     * @return {@link ReturnDocument#BEFORE} if returning the document before it was replaced otherwise return {@link ReturnDocument#AFTER}
     */
    public ReturnDocument getReturnDocument() {
        return returnDocument;
    }

    /**
     * Set whether to return the document before it was replaced or after
     *
     * @param returnDocument set whether to return the document before it was replaced or after
     * @return this
     */
    public FindOneAndReplaceOptions returnDocument(final ReturnDocument returnDocument) {
        this.returnDocument = notNull("returnDocument", returnDocument);
        return this;
    }

    /**
     * Sets the maximum execution time on the server for this operation.
     *
     * @param maxTime  the max time
     * @param timeUnit the time unit, which may not be null
     * @return this
     */
    public FindOneAndReplaceOptions maxTime(final long maxTime, final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        this.maxTimeMS = MILLISECONDS.convert(maxTime, timeUnit);
        return this;
    }

    /**
     * Gets the maximum execution time for the find one and replace operation.
     *
     * @param timeUnit the time unit for the result
     * @return the max time
     */
    public long getMaxTime(final TimeUnit timeUnit) {
        return timeUnit.convert(maxTimeMS, MILLISECONDS);
    }

    /**
     * Gets the the bypass document level validation flag
     *
     * @return the bypass document level validation flag
     * @since 3.2
     * @mongodb.server.release 3.2
     */
    @Nullable
    public Boolean getBypassDocumentValidation() {
        return bypassDocumentValidation;
    }

    /**
     * Sets the bypass document level validation flag.
     *
     * @param bypassDocumentValidation If true, allows the write to opt-out of document level validation.
     * @return this
     * @since 3.2
     * @mongodb.server.release 3.2
     */
    public FindOneAndReplaceOptions bypassDocumentValidation(@Nullable final Boolean bypassDocumentValidation) {
        this.bypassDocumentValidation = bypassDocumentValidation;
        return this;
    }

    /**
     * Returns the collation options
     *
     * @return the collation options
     * @since 3.4
     * @mongodb.server.release 3.4
     */
    @Nullable
    public Collation getCollation() {
        return collation;
    }

    /**
     * Sets the collation options
     *
     *
A null value represents the server default.

     * @param collation the collation options to use
     * @return this
     * @since 3.4
     * @mongodb.server.release 3.4
     */
    public FindOneAndReplaceOptions collation(@Nullable final Collation collation) {
        this.collation = collation;
        return this;
    }

    @Override
    public String toString() {
        return "FindOneAndReplaceOptions{"
                + "projection=" + projection
                + ", sort=" + sort
                + ", upsert=" + upsert
                + ", returnDocument=" + returnDocument
                + ", maxTimeMS=" + maxTimeMS
                + ", bypassDocumentValidation=" + bypassDocumentValidation
                + ", collation=" + collation
                + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.client.jndi;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoException;
import com.mongodb.diagnostics.logging.Logger;
import com.mongodb.diagnostics.logging.Loggers;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import java.util.Enumeration;
import java.util.Hashtable;

import static java.lang.String.format;

/**
 * An ObjectFactory for MongoClient instances.
 *
 * @since 3.3
 */
public class MongoClientFactory implements ObjectFactory {

    private static final Logger LOGGER = Loggers.getLogger("client.jndi");

    private static final String CONNECTION_STRING = "connectionString";

    /**
     * This implementation will create instances of {@link MongoClient} based on a connection string conforming to the format specified in
     * {@link MongoClientURI}.
     *
The connection string is specified in one of two ways:

     *

     *
As the {@code String} value of a property in the {@code environment} parameter with a key of {@code "connectionString"}

     *
As the {@code String} value of a {@link RefAddr} with type {@code "connectionString"} in an {@code obj} parameter
     * of type {@link Reference}

     *

     *
     * Specification of the connection string in the {@code environment} parameter takes precedence over specification in the {@code obj}
     * parameter.  The {@code name} and {@code nameCtx} parameters are ignored.
     *
     * If a non-empty connection string is not specified in either of these two ways, a {@link MongoException} is thrown.
     * @return an instance of {@link MongoClient} based on the specified connection string
     * @throws MongoException
     *
     * Note: Not all options that can be specified via {@link com.mongodb.MongoClientOptions} can be specified via the connection string.
     */
    @Override
    public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, final Hashtable<?, ?> environment)
            throws Exception {

        // Some app servers, e.g. Wildfly, use the environment to pass location information to an ObjectFactory
        String connectionString = null;

        if (environment.get(CONNECTION_STRING) instanceof String) {
            connectionString = (String) environment.get(CONNECTION_STRING);
        }

        if (connectionString == null || connectionString.isEmpty()) {
            LOGGER.debug(format("No '%s' property in environment.  Casting 'obj' to java.naming.Reference to look for a "
                                        + "javax.naming.RefAddr with type equal to '%s'", CONNECTION_STRING, CONNECTION_STRING));

            // Some app servers, e.g. Tomcat, pass obj as an instance of javax.naming.Reference and pass location information in a
            // javax.naming.RefAddr
            if (obj instanceof Reference) {
                Enumeration<RefAddr> props = ((Reference) obj).getAll();

                while (props.hasMoreElements()) {
                    RefAddr addr = props.nextElement();
                    if (addr != null) {
                        if (CONNECTION_STRING.equals(addr.getType())) {
                            if (addr.getContent() instanceof String) {
                                connectionString = (String) addr.getContent();
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (connectionString == null || connectionString.isEmpty()) {
            throw new MongoException(format("Could not locate '%s' in either environment or obj", CONNECTION_STRING));
        }

        MongoClientURI uri = new MongoClientURI(connectionString);

        return new MongoClient(uri);
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.annotations.ThreadSafe;
import com.mongodb.client.internal.MongoIterableImpl;
import com.mongodb.client.internal.OperationExecutor;
import com.mongodb.client.model.Collation;
import com.mongodb.client.model.DBCreateViewOptions;
import com.mongodb.client.model.ValidationAction;
import com.mongodb.client.model.ValidationLevel;
import com.mongodb.connection.BufferProvider;
import com.mongodb.lang.Nullable;
import com.mongodb.operation.BatchCursor;
import com.mongodb.operation.CommandReadOperation;
import com.mongodb.operation.CommandWriteOperation;
import com.mongodb.operation.CreateCollectionOperation;
import com.mongodb.operation.CreateViewOperation;
import com.mongodb.operation.DropDatabaseOperation;
import com.mongodb.operation.ListCollectionsOperation;
import com.mongodb.operation.ReadOperation;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWrapper;
import org.bson.BsonInt32;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.Codec;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.mongodb.DBCollection.createWriteConcernException;
import static com.mongodb.MongoCredential.createCredential;
import static com.mongodb.MongoNamespace.checkDatabaseNameValidity;
import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.notNull;
import static java.util.Arrays.asList;

/**
 * A thread-safe client view of a logical database in a MongoDB cluster. A DB instance can be achieved from a {@link MongoClient} instance
 * using code like:
 *

 * {@code
 * MongoClient mongoClient = new MongoClient();
 * DB db = mongoClient.getDB("<db name>");
 * }
 *

 *
 * See {@link Mongo#getDB(String)} for further information about the effective deprecation of this class.
 *
 * @mongodb.driver.manual reference/glossary/#term-database Database
 * @see MongoClient
 */
@ThreadSafe
@SuppressWarnings("deprecation")
public class DB {
    private final Mongo mongo;
    private final String name;
    private final OperationExecutor executor;
    private final ConcurrentHashMap<String, DBCollection> collectionCache;
    private final Bytes.OptionHolder optionHolder;
    private final Codec<DBObject> commandCodec;
    private volatile ReadPreference readPreference;
    private volatile WriteConcern writeConcern;
    private volatile ReadConcern readConcern;

    DB(final Mongo mongo, final String name, final OperationExecutor executor) {
        checkDatabaseNameValidity(name);
        this.mongo = mongo;
        this.name = name;
        this.executor = executor;
        this.collectionCache = new ConcurrentHashMap<String, DBCollection>();
        this.optionHolder = new Bytes.OptionHolder(mongo.getOptionHolder());
        this.commandCodec = MongoClient.getCommandCodec();
    }

    /**
     * Constructs a new instance of the {@code DB}.
     *
     * @param mongo the mongo instance
     * @param name  the database name - must not be empty and cannot contain spaces
     * @deprecated Prefer {@link MongoClient#getDB(String)}
     */
    @Deprecated
    public DB(final Mongo mongo, final String name) {
        this(mongo, name, mongo.createOperationExecutor());
    }

    /**
     * Gets the Mongo instance
     *
     * @return the mongo instance that this database was created from
     * @deprecated Use {@link #getMongoClient()} instead
     */
    @Deprecated
    public Mongo getMongo() {
        return mongo;
    }

    /**
     * Gets the MongoClient instance
     *
     * @return the MongoClient instance that this database was constructed from
     * @throws IllegalStateException if this DB was not created from a MongoClient instance
     * @since 3.9
     */
    public MongoClient getMongoClient() {
        if (mongo instanceof MongoClient) {
            return (MongoClient) mongo;
        } else {
            throw new IllegalStateException("This DB was not created from a MongoClient.  Use getMongo instead");
        }
    }

    /**
     * Sets the read preference for this database. Will be used as default for read operations from any collection in this database. See the
     * documentation for {@link ReadPreference} for more information.
     *
     * @param readPreference {@code ReadPreference} to use
     * @mongodb.driver.manual core/read-preference/ Read Preference
     */
    public void setReadPreference(final ReadPreference readPreference) {
        this.readPreference = readPreference;
    }

    /**
     * Sets the write concern for this database. It will be used for write operations to any collection in this database. See the
     * documentation for {@link WriteConcern} for more information.
     *
     * @param writeConcern {@code WriteConcern} to use
     * @mongodb.driver.manual core/write-concern/ Write Concern
     */
    public void setWriteConcern(final WriteConcern writeConcern) {
        this.writeConcern = writeConcern;
    }

    /**
     * Gets the read preference for this database.
     *
     * @return {@code ReadPreference} to be used for read operations, if not specified explicitly
     * @mongodb.driver.manual core/read-preference/ Read Preference
     */
    public ReadPreference getReadPreference() {
        return readPreference != null ? readPreference : mongo.getReadPreference();
    }

    /**
     * Gets the write concern for this database.
     *
     * @return {@code WriteConcern} to be used for write operations, if not specified explicitly
     * @mongodb.driver.manual core/write-concern/ Write Concern
     */
    public WriteConcern getWriteConcern() {
        return writeConcern != null ? writeConcern : mongo.getWriteConcern();
    }

    /**
     * Sets the read concern for this database.
     *
     * @param readConcern the read concern to use for this collection
     * @since 3.3
     * @mongodb.server.release 3.2
     * @mongodb.driver.manual reference/readConcern/ Read Concern
     */
    public void setReadConcern(final ReadConcern readConcern) {
        this.readConcern = readConcern;
    }

    /**
     * Get the read concern for this database.
     *
     * @return the {@link com.mongodb.ReadConcern}
     * @since 3.3
     * @mongodb.server.release 3.2
     * @mongodb.driver.manual reference/readConcern/ Read Concern
     */
    public ReadConcern getReadConcern() {
        return readConcern != null ? readConcern : mongo.getReadConcern();
    }

    /**
     *
Gets a collection with a given name. If the collection does not exist, a new collection is created.

     *
     *
This class is NOT part of the public API.  Be prepared for non-binary compatible changes in minor releases.

     *
     * @param name the name of the collection
     * @return the collection
     * @throws IllegalArgumentException if the name is invalid
     * @see MongoNamespace#checkCollectionNameValidity(String)
     * @deprecated there is no replacement for this method
     */
    @Deprecated
    protected DBCollection doGetCollection(final String name) {
        return getCollection(name);
    }

    /**
     * Gets a collection with a given name.
     *
     * @param name the name of the collection to return
     * @return the collection
     * @throws IllegalArgumentException if the name is invalid
     * @see MongoNamespace#checkCollectionNameValidity(String)
     */
    public DBCollection getCollection(final String name) {
        DBCollection collection = collectionCache.get(name);
        if (collection != null) {
            return collection;
        }

        collection = new DBCollection(name, this, executor);
        if (mongo.getMongoClientOptions().getDbDecoderFactory() != DefaultDBDecoder.FACTORY) {
            collection.setDBDecoderFactory(mongo.getMongoClientOptions().getDbDecoderFactory());
        }
        if (mongo.getMongoClientOptions().getDbEncoderFactory() != DefaultDBEncoder.FACTORY) {
            collection.setDBEncoderFactory(mongo.getMongoClientOptions().getDbEncoderFactory());
        }
        DBCollection old = collectionCache.putIfAbsent(name, collection);
        return old != null ? old : collection;
    }

    /**
     * Drops this database. Removes all data on disk. Use with caution.
     *
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual reference/command/dropDatabase/ Drop Database
     */
    public void dropDatabase() {
        try {
            getExecutor().execute(new DropDatabaseOperation(getName(), getWriteConcern()), getReadConcern());
        } catch (MongoWriteConcernException e) {
            throw createWriteConcernException(e);
        }
    }

    /**
     * Returns a collection matching a given string.
     *
     * @param collectionName the name of the collection
     * @return the collection
     * @deprecated Prefer {@link #getCollection(String)}
     */
    @Deprecated
    public DBCollection getCollectionFromString(final String collectionName) {
        return getCollection(collectionName);
    }

    /**
     * Returns the name of this database.
     *
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * Returns a set containing the names of all collections in this database.
     *
     * @return the names of collections in this database
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual reference/method/db.getCollectionNames/ getCollectionNames()
     */
    public Set<String> getCollectionNames() {
        List<String> collectionNames =
                new MongoIterableImpl<DBObject>(null, executor, ReadConcern.DEFAULT, primary(),
                        mongo.getMongoClientOptions().getRetryReads()) {
                    @Override
                    public ReadOperation<BatchCursor<DBObject>> asReadOperation() {
                        return new ListCollectionsOperation<DBObject>(name, commandCodec)
                                .nameOnly(true);
                    }
                }.map(new Function<DBObject, String>() {
                            @Override
                            public String apply(final DBObject result) {
                                return (String) result.get("name");
                            }
                        }).into(new ArrayList<String>());
        Collections.sort(collectionNames);
        return new LinkedHashSet<String>(collectionNames);
    }

    /**
     *
Creates a collection with a given name and options. If the collection already exists,
     * this throws a {@code CommandFailureException}.

     *
     *
Possible options:

     *

     *
 capped ({@code boolean}) - Enables a collection cap. False by default. If enabled,
     *     you must specify a size parameter.

     *
 size ({@code int}) - If capped is true, size specifies a maximum size in bytes for the capped collection. When
     *     capped is false, you may use size to preallocate space.

     *
 max ({@code int}) -   Optional. Specifies a maximum "cap" in number of documents for capped collections. You must
     *     also specify size when specifying max.

     *

     *
Note that if the {@code options} parameter is {@code null}, the creation will be deferred to when the collection is written
     * to.

     *
     * @param collectionName the name of the collection to return
     * @param options        options
     * @return the collection
     * @throws MongoCommandException if the server is unable to create the collection
     * @throws WriteConcernException if the {@code WriteConcern} specified on this {@code DB} could not be satisfied
     * @throws MongoException for all other failures
     * @mongodb.driver.manual reference/method/db.createCollection/ createCollection()
     */
    public DBCollection createCollection(final String collectionName, @Nullable final DBObject options) {
        if (options != null) {
            try {
                executor.execute(getCreateCollectionOperation(collectionName, options), getReadConcern());
            } catch (MongoWriteConcernException e) {
                throw createWriteConcernException(e);
            }
        }
        return getCollection(collectionName);
    }

    /**
     * Creates a view with the given name, backing collection/view name, and aggregation pipeline that defines the view.
     *
     * @param viewName the name of the view to create
     * @param viewOn   the backing collection/view for the view
     * @param pipeline the pipeline that defines the view
     * @return the view as a DBCollection
     * @throws MongoCommandException if the server is unable to create the collection
     * @throws WriteConcernException if the {@code WriteConcern} specified on this {@code DB} could not be satisfied
     * @throws MongoException for all other failures
     * @since 3.4
     * @mongodb.server.release 3.4
     * @mongodb.driver.manual reference/command/create Create Command
     */
    public DBCollection createView(final String viewName, final String viewOn, final List<? extends DBObject> pipeline) {
        return createView(viewName, viewOn, pipeline, new DBCreateViewOptions());
    }

    /**
     * Creates a view with the given name, backing collection/view name, aggregation pipeline, and options that defines the view.
     *
     * @param viewName the name of the view to create
     * @param viewOn   the backing collection/view for the view
     * @param pipeline the pipeline that defines the view
     * @param options  the options for creating the view
     * @return the view as a DBCollection
     * @throws MongoCommandException if the server is unable to create the collection
     * @throws WriteConcernException if the {@code WriteConcern} specified on this {@code DB} could not be satisfied
     * @throws MongoException for all other failures
     * @since 3.4
     * @mongodb.server.release 3.4
     * @mongodb.driver.manual reference/command/create Create Command
     */
    public DBCollection createView(final String viewName, final String viewOn, final List<? extends DBObject> pipeline,
                                   final DBCreateViewOptions options) {
        try {
            notNull("options", options);
            DBCollection view = getCollection(viewName);
            executor.execute(new CreateViewOperation(name, viewName, viewOn, view.preparePipeline(pipeline), writeConcern)
                                     .collation(options.getCollation()), getReadConcern());
            return view;
        } catch (MongoWriteConcernException e) {
            throw createWriteConcernException(e);
        }
    }

    @SuppressWarnings("deprecation")
    private CreateCollectionOperation getCreateCollectionOperation(final String collectionName, final DBObject options) {
        if (options.get("size") != null && !(options.get("size") instanceof Number)) {
            throw new IllegalArgumentException("'size' should be Number");
        }
        if (options.get("max") != null && !(options.get("max") instanceof Number)) {
            throw new IllegalArgumentException("'max' should be Number");
        }
        if (options.get("capped") != null && !(options.get("capped") instanceof Boolean)) {
            throw new IllegalArgumentException("'capped' should be Boolean");
        }
        if (options.get("autoIndexId") != null && !(options.get("autoIndexId") instanceof Boolean)) {
            throw new IllegalArgumentException("'autoIndexId' should be Boolean");
        }
        if (options.get("storageEngine") != null && !(options.get("storageEngine") instanceof DBObject)) {
            throw new IllegalArgumentException("'storageEngine' should be DBObject");
        }
        if (options.get("indexOptionDefaults") != null && !(options.get("indexOptionDefaults") instanceof DBObject)) {
            throw new IllegalArgumentException("'indexOptionDefaults' should be DBObject");
        }
        if (options.get("validator") != null && !(options.get("validator") instanceof DBObject)) {
            throw new IllegalArgumentException("'validator' should be DBObject");
        }
        if (options.get("validationLevel") != null && !(options.get("validationLevel") instanceof String)) {
            throw new IllegalArgumentException("'validationLevel' should be String");
        }
        if (options.get("validationAction") != null && !(options.get("validationAction") instanceof String)) {
            throw new IllegalArgumentException("'validationAction' should be String");
        }

        boolean capped = false;
        boolean autoIndex = true;
        long sizeInBytes = 0;
        long maxDocuments = 0;
        Boolean usePowerOfTwoSizes = null;
        BsonDocument storageEngineOptions = null;
        BsonDocument indexOptionDefaults = null;
        BsonDocument validator = null;
        ValidationLevel validationLevel = null;
        ValidationAction validationAction = null;

        if (options.get("capped") != null) {
            capped = (Boolean) options.get("capped");
        }
        if (options.get("size") != null) {
            sizeInBytes = ((Number) options.get("size")).longValue();
        }
        if (options.get("autoIndexId") != null) {
            autoIndex = (Boolean) options.get("autoIndexId");
        }
        if (options.get("max") != null) {
            maxDocuments = ((Number) options.get("max")).longValue();
        }
        if (options.get("usePowerOfTwoSizes") != null) {
            usePowerOfTwoSizes = (Boolean) options.get("usePowerOfTwoSizes");
        }
        if (options.get("storageEngine") != null) {
            storageEngineOptions = wrap((DBObject) options.get("storageEngine"));
        }
        if (options.get("indexOptionDefaults") != null) {
            indexOptionDefaults = wrap((DBObject) options.get("indexOptionDefaults"));
        }
        if (options.get("validator") != null) {
            validator = wrap((DBObject) options.get("validator"));
        }
        if (options.get("validationLevel") != null) {
            validationLevel = ValidationLevel.fromString((String) options.get("validationLevel"));
        }
        if (options.get("validationAction") != null) {
            validationAction = ValidationAction.fromString((String) options.get("validationAction"));
        }
        Collation collation = DBObjectCollationHelper.createCollationFromOptions(options);
        return new CreateCollectionOperation(getName(), collectionName, getWriteConcern())
                   .capped(capped)
                   .collation(collation)
                   .sizeInBytes(sizeInBytes)
                   .autoIndex(autoIndex)
                   .maxDocuments(maxDocuments)
                   .usePowerOf2Sizes(usePowerOfTwoSizes)
                   .storageEngineOptions(storageEngineOptions)
                   .indexOptionDefaults(indexOptionDefaults)
                   .validator(validator)
                   .validationLevel(validationLevel)
                   .validationAction(validationAction);
    }

    /**
     * Executes a database command. This method constructs a simple DBObject using {@code command} as the field name and {@code true} as its
     * value, and calls {@link DB#command(DBObject, ReadPreference) } with the default read preference for the database.
     *
     * @param command command to execute
     * @return result of command from the database
     * @throws MongoException if the command failed
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     */
    public CommandResult command(final String command) {
        return command(new BasicDBObject(command, Boolean.TRUE), getReadPreference());
    }

    /**
     * Executes a database command. This method calls {@link DB#command(DBObject, ReadPreference) } with the default read preference for the
     * database.
     *
     * @param command {@code DBObject} representation of the command to be executed
     * @return result of the command execution
     * @throws MongoException  if the command failed
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     */
    public CommandResult command(final DBObject command) {
        return command(command, getReadPreference());
    }

    /**
     * Executes a database command. This method calls {@link DB#command(DBObject, ReadPreference, DBEncoder) } with the default read
     * preference for the database.
     *
     * @param command {@code DBObject} representation of the command to be executed
     * @param encoder {@link DBEncoder} to be used for command encoding
     * @return result of the command execution
     * @throws MongoException if the command failed
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     */
    public CommandResult command(final DBObject command, final DBEncoder encoder) {
        return command(command, getReadPreference(), encoder);
    }

    /**
     * Executes a database command with the selected readPreference, and encodes the command using the given encoder.
     *
     * @param command        The {@code DBObject} representation the command to be executed
     * @param readPreference Where to execute the command - this will only be applied for a subset of commands
     * @param encoder        The DBEncoder that knows how to serialise the command
     * @return The result of executing the command, success or failure
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     * @since 2.12
     */
    public CommandResult command(final DBObject command, final ReadPreference readPreference, @Nullable final DBEncoder encoder) {
        try {
            return executeCommand(wrap(command, encoder), getCommandReadPreference(command, readPreference));
        } catch (MongoCommandException ex) {
            return new CommandResult(ex.getResponse(), ex.getServerAddress());
        }
    }

    /**
     * Executes the command against the database with the given read preference.
     *
     * @param command        The {@code DBObject} representation the command to be executed
     * @param readPreference Where to execute the command - this will only be applied for a subset of commands
     * @return The result of executing the command, success or failure
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     * @since 2.12
     */
    public CommandResult command(final DBObject command, final ReadPreference readPreference) {
        return command(command, readPreference, null);
    }

    /**
     * Executes a database command. This method constructs a simple {@link DBObject} and calls {@link DB#command(DBObject, ReadPreference)
     * }.
     *
     * @param command        The name of the command to be executed
     * @param readPreference Where to execute the command - this will only be applied for a subset of commands
     * @return The result of the command execution
     * @throws MongoException if the command failed
     * @mongodb.driver.manual tutorial/use-database-commands Commands
     * @since 2.12
     */
    public CommandResult command(final String command, final ReadPreference readPreference) {
        return command(new BasicDBObject(command, true), readPreference);
    }

    /**
     * Gets another database on same server
     *
     * @param name name of the database
     * @return the DB for the given name
     */
    @SuppressWarnings("deprecation") // The old API (i.e. DB) will use deprecated methods.
    public DB getSisterDB(final String name) {
        return mongo.getDB(name);
    }

    /**
     * Checks to see if a collection with a given name exists on a server.
     *
     * @param collectionName a name of the collection to test for existence
     * @return {@code false} if no collection by that name exists, {@code true} if a match to an existing collection was found
     * @throws MongoException if the operation failed
     */
    public boolean collectionExists(final String collectionName) {
        Set<String> collectionNames = getCollectionNames();
        for (final String name : collectionNames) {
            if (name.equalsIgnoreCase(collectionName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Evaluates JavaScript functions on the database server. This is useful if you need to touch a lot of data lightly, in which case
     * network transfer could be a bottleneck.
     *
     * @param code {@code String} representation of JavaScript function
     * @param args arguments to pass to the JavaScript function
     * @return result of the command execution
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual reference/method/db.eval/ db.eval()
     * @deprecated The eval command was deprecated in MongoDB 3.0
     */
    @Deprecated
    public CommandResult doEval(final String code, final Object... args) {
        DBObject commandDocument = new BasicDBObject("$eval", code).append("args", asList(args));
        return executeCommand(wrap(commandDocument));
    }

    /**
     * Calls {@link DB#doEval(java.lang.String, java.lang.Object[]) }. If the command is successful, the "retval" field is extracted and
     * returned. Otherwise an exception is thrown.
     *
     * @param code {@code String} representation of JavaScript function
     * @param args arguments to pass to the JavaScript function
     * @return result of the execution
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual reference/method/db.eval/ db.eval()
     * @deprecated The eval command was deprecated in MongoDB 3.0
     */
    @Deprecated
    public Object eval(final String code, final Object... args) {
        CommandResult result = doEval(code, args);
        result.throwOnError();
        return result.get("retval");
    }

    /**
     * Helper method for calling a 'dbStats' command. It returns storage statistics for a given database.
     *
     * @return result of the execution
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual reference/command/dbStats/ Database Stats
     * @deprecated Prefer {@link #command(DBObject)} to run the dbStats command
     */
    @Deprecated
    public CommandResult getStats() {
        BsonDocument commandDocument = new BsonDocument("dbStats", new BsonInt32(1)).append("scale", new BsonInt32(1));
        return executeCommand(commandDocument);
    }

    /**
     * Adds or updates a user for this database
     *
     *
Never create or modify users over an insecure network without the use of TLS.

     * @param userName the user name
     * @param password the password
     * @return the result of executing this operation
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual administration/security-access-control/  Access Control
     * @mongodb.driver.manual reference/command/createUser createUser
     * @mongodb.driver.manual reference/command/updateUser updateUser
     * @deprecated Use {@code DB.command} to call either the createUser or updateUser command
     */
    @Deprecated
    public WriteResult addUser(final String userName, final char[] password) {
        return addUser(userName, password, false);
    }

    /**
     * Adds or updates a user for this database
     *
     *
Never create or modify users over an insecure network without the use of TLS.

     * @param userName the user name
     * @param password the password
     * @param readOnly if true, user will only be able to read
     * @return the result of executing this operation
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual administration/security-access-control/  Access Control
     * @mongodb.driver.manual reference/command/createUser createUser
     * @mongodb.driver.manual reference/command/updateUser updateUser
     * @deprecated Use {@code DB.command} to call either the createUser or updateUser command
     */
    @Deprecated
    public WriteResult addUser(final String userName, final char[] password, final boolean readOnly) {
        MongoCredential credential = createCredential(userName, getName(), password);
        boolean userExists = false;
        try {
            userExists = executor.execute(new com.mongodb.operation.UserExistsOperation(getName(), userName), primary(), getReadConcern());
        } catch (MongoCommandException e) {
            if (e.getCode() != 13) {
                throw e;
            }
        }
        try {
            if (userExists) {
                executor.execute(new com.mongodb.operation.UpdateUserOperation(credential, readOnly, getWriteConcern()), getReadConcern());
                return new WriteResult(1, true, null);
            } else {
                executor.execute(new com.mongodb.operation.CreateUserOperation(credential, readOnly, getWriteConcern()), getReadConcern());
                return new WriteResult(1, false, null);
            }
        } catch (MongoWriteConcernException e) {
            throw createWriteConcernException(e);
        }
    }

    /**
     * Removes the specified user from the database.
     *
     * @param userName user to be removed
     * @return the result of executing this operation
     * @throws MongoException if the operation failed
     * @mongodb.driver.manual administration/security-access-control/  Access Control
     * @deprecated Use {@code DB.command} to call the dropUser command
     */
    @Deprecated
    public WriteResult removeUser(final String userName) {
        try {
            executor.execute(new com.mongodb.operation.DropUserOperation(getName(), userName, getWriteConcern()), getReadConcern());
            return new WriteResult(1, true, null);
        } catch (MongoWriteConcernException e) {
            throw createWriteConcernException(e);
        }
    }

    /**
     * Makes it possible to execute "read" queries on a slave node
     *
     * @see ReadPreference#secondaryPreferred()
     * @deprecated Replaced with {@code ReadPreference.secondaryPreferred()}
     */
    @Deprecated
    public void slaveOk() {
        addOption(Bytes.QUERYOPTION_SLAVEOK);
    }

    /**
     * Adds the given flag to the default query options.
     *
     * @param option value to be added
     * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags
     * @deprecated Replaced with {@link DBCursor#addOption(int)}
     */
    @Deprecated
    public void addOption(final int option) {
        optionHolder.add(option);
    }

    /**
     * Sets the query options, overwriting previous value.
     *
     * @param options bit vector of query options
     * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags
     * @deprecated Replaced with {@link DBCursor#setOptions(int)}
     */
    @Deprecated
    public void setOptions(final int options) {
        optionHolder.set(options);
    }

    /**
     * Resets the query options.
     * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags
     * @deprecated Replaced with {@link DBCursor#resetOptions()}
     */
    @Deprecated
    public void resetOptions() {
        optionHolder.reset();
    }

    /**
     * Gets the query options
     *
     * @return bit vector of query options
     * @mongodb.driver.manual ../meta-driver/latest/legacy/mongodb-wire-protocol/#op-query Query Flags
     * @deprecated Replaced with {@link DBCursor#getOptions()}
     */
    @Deprecated
    public int getOptions() {
        return optionHolder.get();
    }

    @Override
    public String toString() {
        return "DB{name='" + name + '\'' + '}';
    }

    CommandResult executeCommand(final BsonDocument commandDocument) {
        return new CommandResult(executor.execute(new CommandWriteOperation<BsonDocument>(getName(), commandDocument,
                                                                                new BsonDocumentCodec()), getReadConcern()));
    }

    CommandResult executeCommand(final BsonDocument commandDocument, final ReadPreference readPreference) {
        return new CommandResult(executor.execute(new CommandReadOperation<BsonDocument>(getName(), commandDocument,
                                                                                new BsonDocumentCodec()),
                                                  readPreference, getReadConcern()));
    }

    OperationExecutor getExecutor() {
        return executor;
    }

    Bytes.OptionHolder getOptionHolder() {
        return optionHolder;
    }

    BufferProvider getBufferPool() {
        return getMongo().getBufferProvider();
    }

    private BsonDocument wrap(final DBObject document) {
        return new BsonDocumentWrapper<DBObject>(document, commandCodec);
    }

    private BsonDocument wrap(final DBObject document, @Nullable final DBEncoder encoder) {
        if (encoder == null) {
            return wrap(document);
        } else {
            return new BsonDocumentWrapper<DBObject>(document, new DBEncoderAdapter(encoder));
        }
    }

    /**
     * Determines the read preference that should be used for the given command.
     *
     * @param command             the {@link DBObject} representing the command
     * @param requestedPreference the preference requested by the client.
     * @return the read preference to use for the given command.  It will never return {@code null}.
     * @see com.mongodb.ReadPreference
     */
    ReadPreference getCommandReadPreference(final DBObject command, @Nullable final ReadPreference requestedPreference) {
        String comString = command.keySet().iterator().next().toLowerCase();
        boolean primaryRequired = !OBEDIENT_COMMANDS.contains(comString);

        if (primaryRequired) {
            return ReadPreference.primary();
        } else if (requestedPreference == null) {
            return ReadPreference.primary();
        } else {
            return requestedPreference;
        }
    }

    private static final Set<String> OBEDIENT_COMMANDS = new HashSet<String>();

    static {
        OBEDIENT_COMMANDS.add("aggregate");
        OBEDIENT_COMMANDS.add("collstats");
        OBEDIENT_COMMANDS.add("count");
        OBEDIENT_COMMANDS.add("dbstats");
        OBEDIENT_COMMANDS.add("distinct");
        OBEDIENT_COMMANDS.add("geonear");
        OBEDIENT_COMMANDS.add("geosearch");
        OBEDIENT_COMMANDS.add("geowalk");
        OBEDIENT_COMMANDS.add("group");
        OBEDIENT_COMMANDS.add("listcollections");
        OBEDIENT_COMMANDS.add("listindexes");
        OBEDIENT_COMMANDS.add("parallelcollectionscan");
        OBEDIENT_COMMANDS.add("text");
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 gridfs;

import com.mongodb.Block;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.GridFSUploadStream;
import com.mongodb.client.gridfs.model.GridFSDownloadOptions;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.mongodb.client.gridfs.model.GridFSUploadOptions;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import static com.mongodb.client.model.Filters.eq;

/**
 * The GridFS code example see: https://mongodb.github.io/mongo-java-driver/3.1/driver/reference/gridfs
 */
public final class GridFSTour {

    /**
     * Run this main method to see the output of this quick example.
     *
     * @param args takes an optional single argument for the connection string
     * @throws FileNotFoundException if the sample file cannot be found
     * @throws IOException if there was an exception closing an input stream
     */
    public static void main(final String[] args) throws FileNotFoundException, IOException {
        MongoClient mongoClient;

        if (args.length == 0) {
            // connect to the local database server
            mongoClient = MongoClients.create();
        } else {
            mongoClient = MongoClients.create(args[0]);
        }

        // get handle to "mydb" database
        MongoDatabase database = mongoClient.getDatabase("mydb");
        database.drop();

        GridFSBucket gridFSBucket = GridFSBuckets.create(database);

        /*
         * UploadFromStream Example
         */
        // Get the input stream
        InputStream streamToUploadFrom = new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8));

        // Create some custom options
        GridFSUploadOptions options = new GridFSUploadOptions()
                .chunkSizeBytes(1024)
                .metadata(new Document("type", "presentation"));

        ObjectId fileId = gridFSBucket.uploadFromStream("mongodb-tutorial", streamToUploadFrom, options);
        streamToUploadFrom.close();
        System.out.println("The fileId of the uploaded file is: " + fileId.toHexString());

        /*
         * OpenUploadStream Example
         */

        // Get some data to write
        byte[] data = "Data to upload into GridFS".getBytes(StandardCharsets.UTF_8);

        GridFSUploadStream uploadStream = gridFSBucket.openUploadStream("sampleData");
        uploadStream.write(data);
        uploadStream.close();
        System.out.println("The fileId of the uploaded file is: " + uploadStream.getObjectId().toHexString());

        /*
         * Find documents
         */
        gridFSBucket.find().forEach(new Block<GridFSFile>() {
            @Override
            public void apply(final GridFSFile gridFSFile) {
                System.out.println(gridFSFile.getFilename());
            }
        });

        /*
         * Find documents with a filter
         */
        gridFSBucket.find(eq("metadata.contentType", "image/png")).forEach(
                new Block<GridFSFile>() {
                    @Override
                    public void apply(final GridFSFile gridFSFile) {
                        System.out.println(gridFSFile.getFilename());
                    }
                });

        /*
         * DownloadToStream
         */
        FileOutputStream streamToDownloadTo = new FileOutputStream("/tmp/mongodb-tutorial.txt");
        gridFSBucket.downloadToStream(fileId, streamToDownloadTo);
        streamToDownloadTo.close();

        /*
         * DownloadToStreamByName
         */
        streamToDownloadTo = new FileOutputStream("/tmp/mongodb-tutorial.txt");
        GridFSDownloadOptions downloadOptions = new GridFSDownloadOptions().revision(0);
        gridFSBucket.downloadToStream("mongodb-tutorial", streamToDownloadTo, downloadOptions);
        streamToDownloadTo.close();

        /*
         * OpenDownloadStream
         */
        GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(fileId);
        int fileLength = (int) downloadStream.getGridFSFile().getLength();
        byte[] bytesToWriteTo = new byte[fileLength];
        downloadStream.read(bytesToWriteTo);
        downloadStream.close();

        System.out.println(new String(bytesToWriteTo, StandardCharsets.UTF_8));

        /*
         * OpenDownloadStreamByName
         */

        downloadStream = gridFSBucket.openDownloadStream("sampleData");
        fileLength = (int) downloadStream.getGridFSFile().getLength();
        bytesToWriteTo = new byte[fileLength];
        downloadStream.read(bytesToWriteTo);
        downloadStream.close();

        System.out.println(new String(bytesToWriteTo, StandardCharsets.UTF_8));

        /*
         * Rename
         */
        gridFSBucket.rename(fileId, "mongodbTutorial");

        /*
         * Delete
         */
        gridFSBucket.delete(fileId);

        database.drop();
    }

    private GridFSTour() {
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.bson.codecs.configuration;

import org.bson.codecs.Codec;

import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;

/**
 * A helper class for creating and combining codecs, codec providers, and codec registries
 *
 * @since 3.0
 */
public final class CodecRegistries {

    /**
     * Creates a {@code CodecRegistry} from the provided list of {@code Codec} instances.
     *
     *
This registry can then be used alongside other registries. Typically used when adding extra codecs to existing codecs with the
     * {@link #fromRegistries(CodecRegistry...)} )} helper.

     *
     * @param codecs the {@code Codec} to create a registry for
     * @return a {@code CodecRegistry} for the given list of {@code Codec} instances.
     */
    public static CodecRegistry fromCodecs(final Codec<?>... codecs) {
        return fromCodecs(asList(codecs));
    }

    /**
     * Creates a {@code CodecRegistry} from the provided list of {@code Codec} instances.
     *
     *
This registry can then be used alongside other registries.  Typically used when adding extra codecs to existing codecs with the
     * {@link #fromRegistries(CodecRegistry...)} )} helper.

     *
     * @param codecs the {@code Codec} to create a registry for
     * @return a {@code CodecRegistry} for the given list of {@code Codec} instances.
     */
    public static CodecRegistry fromCodecs(final List<? extends Codec<?>> codecs) {
        return fromProviders(new MapOfCodecsProvider(codecs));
    }

    /**
     * Creates a {@code CodecRegistry} from the provided list of {@code CodecProvider} instances.
     *
     *
The created instance can handle cycles of {@code Codec} dependencies, i.e when the construction of a {@code Codec} for class A
     * requires the construction of a {@code Codec} for class B, and vice versa.

     *
     * @param providers the codec provider
     * @return a {@code CodecRegistry} with the ordered list of {@code CodecProvider} instances. The registry is also guaranteed to be an
     * instance of {code CodecProvider}, so that when one is passed to {@link #fromRegistries(CodecRegistry...)} or {@link
     * #fromRegistries(java.util.List)} it will be treated as a {@code CodecProvider} and properly resolve any dependencies between
     * registries.
     */
    public static CodecRegistry fromProviders(final CodecProvider... providers) {
        return fromProviders(asList(providers));
    }

    /**
     * Creates a {@code CodecRegistry} from the provided list of {@code CodecProvider} instances.
     *
     *
The created instance can handle cycles of {@code Codec} dependencies, i.e when the construction of a {@code Codec} for class A
     * requires the construction of a {@code Codec} for class B, and vice versa.

     *
     * @param providers the codec provider
     * @return a {@code CodecRegistry} with the ordered list of {@code CodecProvider} instances. The registry is also guaranteed to be an
     * instance of {code CodecProvider}, so that when one is passed to {@link #fromRegistries(CodecRegistry...)} or {@link
     * #fromRegistries(java.util.List)} it will be treated as a {@code CodecProvider} and properly resolve any dependencies between
     * registries.
     */
    public static CodecRegistry fromProviders(final List<? extends CodecProvider> providers) {
        return new ProvidersCodecRegistry(providers);
    }

    /**
     * A {@code CodecRegistry} that combines the given {@code CodecRegistry} instances into a single registry.
     *
     *
The registries are checked in order until one returns a {@code Codec} for the requested {@code Class}.

     *
     *
The created instance can handle cycles of {@code Codec} dependencies, i.e when the construction of a {@code Codec} for class A
     * requires the construction of a {@code Codec} for class B, and vice versa.

     *
Any of the given registries that also implement {@code CodecProvider} will be treated as a {@code CodecProvider} instead of a
     * {@code CodecRegistry}, which will ensure proper resolution of any dependencies between registries.

     *
     * @param registries the preferred registry for {@code Codec} lookups
     *
     * @return a {@code CodecRegistry} that combines the list of {@code CodecRegistry} instances into a single one
     */
    public static CodecRegistry fromRegistries(final CodecRegistry... registries) {
        return fromRegistries(asList(registries));
    }

    /**
     * A {@code CodecRegistry} that combines the given {@code CodecRegistry} instances into a single registry.
     *
     *
The registries are checked in order until one returns a {@code Codec} for the requested {@code Class}.

     *
     *
The created instance can handle cycles of {@code Codec} dependencies, i.e when the construction of a {@code Codec} for class A
     * requires the construction of a {@code Codec} for class B, and vice versa.

     *
Any of the given registries that also implement {@code CodecProvider} will be treated as a {@code CodecProvider} instead of a
     * {@code CodecRegistry}, which will ensure proper resolution of any dependencies between registries.

     *
     * @param registries the preferred registry for {@code Codec} lookups
     *
     * @return a {@code CodecRegistry} that combines the list of {@code CodecRegistry} instances into a single one
     */
    public static CodecRegistry fromRegistries(final List<? extends CodecRegistry> registries) {
        List<CodecProvider> providers = new ArrayList<CodecProvider>();
        for (CodecRegistry registry : registries) {
            providers.add(providerFromRegistry(registry));
        }
        return new ProvidersCodecRegistry(providers);
    }

    private static CodecProvider providerFromRegistry(final CodecRegistry innerRegistry) {
        if (innerRegistry instanceof CodecProvider) {
            return (CodecProvider) innerRegistry;
        } else {
            return new CodecProvider() {
                @Override
                public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry outerRregistry) {
                    try {
                        return innerRegistry.get(clazz);
                    } catch (CodecConfigurationException e) {
                        return null;
                    }
                }
            };
        }
    }

    private CodecRegistries() {
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.bson.codecs.configuration;

import org.bson.codecs.Codec;

// An implementation of CodecRegistry that is used to detect cyclic dependencies between Codecs
class ChildCodecRegistry<T> implements CodecRegistry {

    private final ChildCodecRegistry<?> parent;
    private final ProvidersCodecRegistry registry;
    private final Class<T> codecClass;

    ChildCodecRegistry(final ProvidersCodecRegistry registry, final Class<T> codecClass) {
        this.codecClass = codecClass;
        this.parent = null;
        this.registry = registry;
    }

    private ChildCodecRegistry(final ChildCodecRegistry<?> parent, final Class<T> codecClass) {
        this.parent = parent;
        this.codecClass = codecClass;
        this.registry = parent.registry;
    }

    public Class<T> getCodecClass() {
        return codecClass;
    }

    // Gets a Codec, but if it detects a cyclic dependency, return a LazyCodec which breaks the chain.
    public <U> Codec<U> get(final Class<U> clazz) {
        if (hasCycles(clazz)) {
            return new LazyCodec<U>(registry, clazz);
        } else {
            return registry.get(new ChildCodecRegistry<U>(this, clazz));
        }
    }

    @SuppressWarnings("rawtypes")
    private <U> Boolean hasCycles(final Class<U> theClass) {
        ChildCodecRegistry current = this;
        while (current != null) {
            if (current.codecClass.equals(theClass)) {
                return true;
            }

            current = current.parent;
        }

        return false;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ChildCodecRegistry<?> that = (ChildCodecRegistry) o;

        if (!codecClass.equals(that.codecClass)) {
            return false;
        }
        if (parent != null ? !parent.equals(that.parent) : that.parent != null) {
            return false;
        }
        if (!registry.equals(that.registry)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = parent != null ? parent.hashCode() : 0;
        result = 31 * result + registry.hashCode();
        result = 31 * result + codecClass.hashCode();
        return result;
    }
}