/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.mongodb.commands.pojos; import com.eightkdata.mongowp.bson.BsonArray; import com.eightkdata.mongowp.bson.BsonDocument; import com.eightkdata.mongowp.bson.BsonDocument.Entry; import com.eightkdata.mongowp.bson.BsonValue; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.NoSuchKeyException; import com.eightkdata.mongowp.exceptions.TypesMismatchException; import com.eightkdata.mongowp.fields.ArrayField; import com.eightkdata.mongowp.fields.BooleanField; import com.eightkdata.mongowp.fields.DocField; import com.eightkdata.mongowp.fields.IntField; import com.eightkdata.mongowp.fields.LongField; import com.eightkdata.mongowp.utils.BsonArrayBuilder; import com.eightkdata.mongowp.utils.BsonDocumentBuilder; import com.eightkdata.mongowp.utils.BsonReaderTool; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; public abstract class CollectionOptions { private static final BooleanField CAPPED_FIELD = new BooleanField("capped"); private static final LongField CAPPED_SIZE_FIELD = new LongField("size"); private static final LongField CAPPED_MAX_DOCS_FIELD = new LongField("max"); private static final ArrayField INITIAL_EXTENT_SIZES_FIELD = new ArrayField("$nExtents"); private static final LongField INITIAL_NUM_EXTENDS_FIELD = new LongField("$nExtents"); private static final BooleanField AUTO_INDEX_MODE_FIELD = new BooleanField("autoIndexId"); private static final IntField FLAGS_FIELD = new IntField("flags"); private static final DocField STORAGE_ENGINE_FIELD = new DocField("storageEngine"); private static final BooleanField TEMP_FIELD = new BooleanField("temp"); public abstract boolean isCapped(); public abstract long getCappedSize(); public abstract long getCappedMaxDocs(); public abstract Long getInitialNumExtents(); public abstract List<Long> getInitialExtentSizes(); public abstract AutoIndexMode getAutoIndexMode(); public abstract Set<Flag> getFlags(); public abstract BsonDocument getStorageEngine(); public abstract boolean isTemp(); public BsonDocument marshall() { BsonDocumentBuilder builder = new BsonDocumentBuilder(); marshall(builder); return builder.build(); } public void marshall(BsonDocumentBuilder builder) { if (isCapped()) { builder.append(CAPPED_FIELD, true); long cappedSize = getCappedSize(); if (cappedSize != 0) { builder.append(CAPPED_SIZE_FIELD, cappedSize); } long cappedMaxDocs = getCappedMaxDocs(); if (cappedMaxDocs != 0) { builder.append(CAPPED_MAX_DOCS_FIELD, cappedMaxDocs); } } Long initialNumExtents = getInitialNumExtents(); if (initialNumExtents != null && initialNumExtents != 0) { builder.append(INITIAL_NUM_EXTENDS_FIELD, initialNumExtents); } List<Long> initialExtentSizes = getInitialExtentSizes(); if (initialExtentSizes != null && !initialExtentSizes.isEmpty()) { BsonArrayBuilder arrBuilder = new BsonArrayBuilder(initialExtentSizes.size()); for (Long initialExtentSize : initialExtentSizes) { arrBuilder.add(initialExtentSize); } builder.append(INITIAL_EXTENT_SIZES_FIELD, arrBuilder.build()); } AutoIndexMode autoIndexMode = getAutoIndexMode(); if (!autoIndexMode.equals(AutoIndexMode.DEFAULT)) { boolean value = autoIndexMode.equals(AutoIndexMode.YES); builder.append(AUTO_INDEX_MODE_FIELD, value); } Set<Flag> flags = getFlags(); if (!flags.isEmpty()) { int value = 0; for (Flag flag : flags) { value |= flag.getBit(); } builder.append(FLAGS_FIELD, value); } BsonDocument storageEngine = getStorageEngine(); if (storageEngine != null && storageEngine.isEmpty()) { builder.append(STORAGE_ENGINE_FIELD, storageEngine); } boolean temp = isTemp(); if (temp) { builder.append(TEMP_FIELD, temp); } } public static CollectionOptions unmarshal(BsonDocument doc) throws BadValueException { boolean capped; try { capped = BsonReaderTool.getBooleanOrNumeric(doc, CAPPED_FIELD, false); } catch (TypesMismatchException ex) { capped = true; } long cappedSize; try { cappedSize = BsonReaderTool.getLong(doc, CAPPED_SIZE_FIELD, 0); if (cappedSize < 0) { throw new BadValueException("size has to be >= 0"); } } catch (TypesMismatchException ex) { cappedSize = 0; //backward compatibility } long cappedMaxDocs; try { cappedMaxDocs = BsonReaderTool.getLong(doc, CAPPED_MAX_DOCS_FIELD, 0); if (cappedMaxDocs < 0) { throw new BadValueException("max has to be >= 0"); } } catch (TypesMismatchException ex) { cappedMaxDocs = 0; //backward compatibility } final ImmutableList.Builder<Long> initialExtentSizes = ImmutableList.builder(); Long initialNumExtends; BsonArray array; try { array = BsonReaderTool.getArray(doc, INITIAL_EXTENT_SIZES_FIELD, null); if (array == null) { initialNumExtends = null; } else { initialNumExtends = null; for (int i = 0; i < array.size(); i++) { BsonValue element = array.get(i); if (!element.isNumber()) { throw new BadValueException("'$nExtents'.'" + i + "' has " + "the value " + element.toString() + " which is " + "not a number"); } initialExtentSizes.add(element.asNumber().longValue()); } } } catch (TypesMismatchException ex) { try { initialNumExtends = BsonReaderTool.getLong(doc, INITIAL_NUM_EXTENDS_FIELD); } catch (NoSuchKeyException | TypesMismatchException ex1) { initialNumExtends = null; } } AutoIndexMode autoIndexMode; try { if (BsonReaderTool.getBoolean(doc, AUTO_INDEX_MODE_FIELD)) { autoIndexMode = AutoIndexMode.YES; } else { autoIndexMode = AutoIndexMode.NO; } } catch (NoSuchKeyException | TypesMismatchException ex) { autoIndexMode = AutoIndexMode.DEFAULT; } EnumSet<Flag> flags = EnumSet.noneOf(Flag.class); try { int flagInt = BsonReaderTool.getInteger(doc, FLAGS_FIELD, 0); for (Flag value : Flag.values()) { if ((flagInt & value.bit) != 0) { flags.add(value); } } } catch (TypesMismatchException ignore) { //Nothing to do here } BsonDocument storageEngine; try { storageEngine = BsonReaderTool.getDocument(doc, STORAGE_ENGINE_FIELD, null); } catch (TypesMismatchException ex) { throw new BadValueException("'storageEngine' has to be a document."); } if (storageEngine != null) { if (storageEngine.isEmpty()) { throw new BadValueException( "Empty 'storageEngine' options are invalid. " + "Please remove, or include valid options"); } for (Entry<?> entry : storageEngine) { if (!entry.getValue().isDocument()) { throw new BadValueException("'storageEngie'.'" + entry.getKey() + "' has to be an embedded document"); } } } boolean temp; try { temp = BsonReaderTool.getBoolean(doc, TEMP_FIELD, false); } catch (TypesMismatchException ex) { throw new BadValueException("Temp field must be a boolean"); } return new DefaultCollectionOptions( capped, cappedSize, cappedMaxDocs, initialNumExtends, initialExtentSizes.build(), autoIndexMode, flags, storageEngine, temp ); } public static enum AutoIndexMode { /** * {@linkplain #YES} for most collections, {@linkplain #NO} for some system ones */ DEFAULT, YES, NO } public static enum Flag { USE_POWER_OF_2(0), NO_PADDING(1); private final int bit; private Flag(int bit) { this.bit = bit; } public int getBit() { return bit; } public static Flag fromBit(int bit) { for (Flag value : Flag.values()) { if (value.bit == bit) { return value; } } throw new IllegalArgumentException("There is no collection flag " + "whose bit is equal to '" + bit + "'"); } } public static class Builder { private boolean capped; private long cappedSize; private long cappedMaxDocs; private Long initialNumExtents; private List<Long> initialExtentSizes; private AutoIndexMode autoIndexMode; private EnumSet<Flag> flags; private BsonDocument storageEngine; private boolean temp; public Builder setCapped(boolean capped) { this.capped = capped; return this; } public Builder setCappedSize(long cappedSize) { Preconditions.checkState(capped || cappedSize == 0); this.cappedSize = cappedSize; return this; } public Builder setCappedMaxDocs(long cappedMaxDocs) { Preconditions.checkState(capped || cappedMaxDocs == 0); this.cappedMaxDocs = cappedMaxDocs; return this; } public Builder setInitialNumExtents(Long initialNumExtents) { Preconditions.checkState(initialExtentSizes == null); this.initialNumExtents = initialNumExtents; return this; } public Builder setInitialExtentSizes(List<Long> initialExtentSizes) { Preconditions.checkState(initialNumExtents == null); this.initialExtentSizes = initialExtentSizes; return this; } public Builder setAutoIndexMode(@Nonnull AutoIndexMode autoIndexMode) { this.autoIndexMode = autoIndexMode; return this; } public Builder setFlags(EnumSet<Flag> flags) { this.flags = flags; return this; } public Builder setStorageEngine(BsonDocument storageEngine) { this.storageEngine = storageEngine; return this; } public Builder setTemp(boolean temp) { this.temp = temp; return this; } public CollectionOptions build() { return new DefaultCollectionOptions( capped, cappedSize, cappedMaxDocs, initialNumExtents, initialExtentSizes, autoIndexMode, flags, storageEngine, temp ); } } private static class DefaultCollectionOptions extends CollectionOptions { private final boolean capped; private final long cappedSize; private final long cappedMaxDocs; private final Long initialNumExtents; private final ImmutableList<Long> initialExtentSizes; private final AutoIndexMode autoIndexMode; private final Set<Flag> flags; private final BsonDocument storageEngine; private final boolean temp; private DefaultCollectionOptions( boolean capped, long cappedSize, long cappedMaxDocs, Long initialNumExtents, List<Long> initialExtentSizes, AutoIndexMode autoIndexMode, Set<Flag> flags, BsonDocument storageEngine, boolean temp) { assert capped || cappedSize == 0 && cappedMaxDocs == 0; assert initialNumExtents == null || initialExtentSizes == null; this.capped = capped; this.cappedSize = cappedSize; this.cappedMaxDocs = cappedMaxDocs; this.initialNumExtents = initialNumExtents; this.initialExtentSizes = initialExtentSizes != null ? ImmutableList .copyOf(initialExtentSizes) : null; this.autoIndexMode = autoIndexMode; this.flags = flags != null ? flags : Collections.emptySet(); this.storageEngine = storageEngine; this.temp = temp; } @Override public boolean isCapped() { return capped; } @Override public long getCappedSize() { return cappedSize; } @Override public long getCappedMaxDocs() { return cappedMaxDocs; } @Override public Long getInitialNumExtents() { return initialNumExtents; } @Override public ImmutableList<Long> getInitialExtentSizes() { return initialExtentSizes; } @Override public AutoIndexMode getAutoIndexMode() { return autoIndexMode; } @Override public Set<Flag> getFlags() { return flags; } @Override public BsonDocument getStorageEngine() { return storageEngine; } @Override public boolean isTemp() { return temp; } } }