| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.cassandra.db.marshal; |
| |
| import java.nio.ByteBuffer; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.cassandra.cql3.CQL3Type; |
| import org.apache.cassandra.cql3.Constants; |
| import org.apache.cassandra.cql3.Term; |
| import org.apache.cassandra.io.util.DataInputPlus; |
| import org.apache.cassandra.io.util.DataOutputPlus; |
| import org.apache.cassandra.serializers.TypeSerializer; |
| import org.apache.cassandra.serializers.EmptySerializer; |
| import org.apache.cassandra.serializers.MarshalException; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| import org.apache.cassandra.utils.NoSpamLogger; |
| |
| /** |
| * A type that only accept empty data. |
| * It is only useful as a value validation type, not as a comparator since column names can't be empty. |
| */ |
| public class EmptyType extends AbstractType<Void> |
| { |
| private enum NonEmptyWriteBehavior { FAIL, LOG_DATA_LOSS, SILENT_DATA_LOSS } |
| |
| private static final Logger logger = LoggerFactory.getLogger(EmptyType.class); |
| private static final String KEY_EMPTYTYPE_NONEMPTY_BEHAVIOR = "cassandra.serialization.emptytype.nonempty_behavior"; |
| private static final NoSpamLogger NON_EMPTY_WRITE_LOGGER = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES); |
| private static final NonEmptyWriteBehavior NON_EMPTY_WRITE_BEHAVIOR = parseNonEmptyWriteBehavior(); |
| |
| private static NonEmptyWriteBehavior parseNonEmptyWriteBehavior() |
| { |
| String value = System.getProperty(KEY_EMPTYTYPE_NONEMPTY_BEHAVIOR); |
| if (value == null) |
| return NonEmptyWriteBehavior.FAIL; |
| try |
| { |
| return NonEmptyWriteBehavior.valueOf(value.toUpperCase().trim()); |
| } |
| catch (Exception e) |
| { |
| logger.warn("Unable to parse property " + KEY_EMPTYTYPE_NONEMPTY_BEHAVIOR + ", falling back to FAIL", e); |
| return NonEmptyWriteBehavior.FAIL; |
| } |
| } |
| |
| public static final EmptyType instance = new EmptyType(); |
| |
| private EmptyType() {super(ComparisonType.CUSTOM);} // singleton |
| |
| public int compareCustom(ByteBuffer o1, ByteBuffer o2) |
| { |
| return 0; |
| } |
| |
| public String getString(ByteBuffer bytes) |
| { |
| return ""; |
| } |
| |
| public ByteBuffer fromString(String source) throws MarshalException |
| { |
| if (!source.isEmpty()) |
| throw new MarshalException(String.format("'%s' is not empty", source)); |
| |
| return ByteBufferUtil.EMPTY_BYTE_BUFFER; |
| } |
| |
| @Override |
| public Term fromJSONObject(Object parsed) throws MarshalException |
| { |
| if (!(parsed instanceof String)) |
| throw new MarshalException(String.format("Expected an empty string, but got: %s", parsed)); |
| if (!((String) parsed).isEmpty()) |
| throw new MarshalException(String.format("'%s' is not empty", parsed)); |
| |
| return new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER); |
| } |
| |
| @Override |
| public CQL3Type asCQL3Type() |
| { |
| return CQL3Type.Native.EMPTY; |
| } |
| |
| public String toJSONString(ByteBuffer buffer, int protocolVersion) |
| { |
| return "\"\""; |
| } |
| |
| public TypeSerializer<Void> getSerializer() |
| { |
| return EmptySerializer.instance; |
| } |
| |
| @Override |
| protected int valueLengthIfFixed() |
| { |
| return 0; |
| } |
| |
| @Override |
| public ByteBuffer readValue(DataInputPlus in) |
| { |
| return ByteBufferUtil.EMPTY_BYTE_BUFFER; |
| } |
| |
| @Override |
| public ByteBuffer readValue(DataInputPlus in, int maxValueSize) |
| { |
| return ByteBufferUtil.EMPTY_BYTE_BUFFER; |
| } |
| |
| @Override |
| public void writeValue(ByteBuffer value, DataOutputPlus out) |
| { |
| if (!value.hasRemaining()) |
| return; |
| // In 3.0 writeValue was added which required EmptyType to write data, and relied on caller to never do that; |
| // that behavior was unsafe so guard against it. There are configurable behaviors, but the only allowed cases |
| // should be *_DATA_LOSS (last resort... really should avoid this) and fail; fail should be preferred in nearly |
| // all cases. |
| // see CASSANDRA-15790 |
| switch (NON_EMPTY_WRITE_BEHAVIOR) |
| { |
| case LOG_DATA_LOSS: |
| NON_EMPTY_WRITE_LOGGER.warn("Dropping data...", new NonEmptyWriteException("Attempted to write a non-empty value using EmptyType")); |
| case SILENT_DATA_LOSS: |
| return; |
| case FAIL: |
| default: |
| throw new AssertionError("Attempted to write a non-empty value using EmptyType"); |
| } |
| } |
| |
| private static final class NonEmptyWriteException extends RuntimeException |
| { |
| NonEmptyWriteException(String message) |
| { |
| super(message); |
| } |
| } |
| } |