All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.contexts.ContextSerializationHelper Maven / Gradle / Ivy

There is a newer version: 0.5.1
Show newest version
/*
 * Copyright 2014 Netflix, 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 io.reactivex.netty.contexts;

import com.netflix.server.context.ContextSerializationException;
import com.netflix.server.context.ContextSerializer;
import com.netflix.server.context.DirectionAwareContextSerializer;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * A helper class to aid contexts serialization/de-serialization. 

* *

Serialization Format:

* * Any context sent over the wire will be sent in the following format:
    [serialization_format_version][separator][fully qualified class name of the serializer][separator][serializer version][separator][serialized context data]
 
* * The [serialization_format_version] above is {@link ContextSerializationHelper#SERIALIZATION_FORMAT_VER}

* The [separator] above is {@link ContextSerializationHelper#CONTEXT_SERIALIZATION_PROPS_SEPARATOR}

* * This class also adds a separate key by the name {@link #ALL_CONTEXTS_NAMES_KEY_NAME} to have a * {@link ContextSerializationHelper#CONTEXT_HEADER_NAMES_SEPARATOR} separated list of key names corresponding to the * contexts. * * @author Nitesh Kant ([email protected]) */ final class ContextSerializationHelper { /* * Key name prefix for any contexts present as part of the wire-protocol. eg: For HTTP it will be the * prefix for the key name. */ static final String CONTEXT_PROTOCOL_KEY_NAME_PREFIX = "X-Netflix.request.sub.context."; /* * Key name for a list of all context keys added to the outbound service request. This is used to avoid * storing the request instance for lazy fetching of the contexts. We will get all the * context headers aggressively in construction and store the serialized form locally. */ static final String ALL_CONTEXTS_NAMES_KEY_NAME = "X-Netflix.request.sub.context.names"; @SuppressWarnings("unused") private static final int CONTEXT_SERIALIZATION_PROPS_SERIALIZATION_FORMAT_VER_INDEX = 0; private static final int CONTEXT_SERIALIZATION_PROPS_SERIALIZER_INDEX = 1; private static final int CONTEXT_SERIALIZATION_PROPS_VERSION_INDEX = 2; private static final int CONTEXT_SERIALIZATION_PROPS_DATA_INDEX = 3; private static final int CONTEXT_SERIALIZATION_PROPS_COUNT = 4; private static final String CONTEXT_SERIALIZATION_PROPS_SEPARATOR_REGEX = "\\|"; static final int SERIALIZATION_FORMAT_VER = 1; static final String CONTEXT_SERIALIZATION_PROPS_SEPARATOR = "|"; static final String CONTEXT_HEADER_NAMES_SEPARATOR = ","; public static final String EMPTY_SERIALIZED_DATA = "EMPTY"; private static final Pattern CONTEXT_HEADER_NAMES_SEPARATOR_PATTERN = Pattern.compile(CONTEXT_HEADER_NAMES_SEPARATOR); private static final Pattern CONTEXT_SERIALIZATION_PROPS_SEPARATOR_PATTERN = Pattern.compile(CONTEXT_SERIALIZATION_PROPS_SEPARATOR_REGEX); private ContextSerializationHelper() { } /** * Serializes all contexts present in the passed {@code contextsContainer}. This also adds a index * key for the name of all the headers added corresponding to the contained contexts. See * {@link ContextSerializationHelper} for serialization format. * * @param contextHolders {@link ContextHolder} instances to be serialized. * * @return Immutable map of key name against the serialized contexts. Empty map if no contexts are * present. */ static Map getSerializedContexts(@SuppressWarnings("rawtypes") Collection contextHolders) throws ContextSerializationException { return getSerializedHeader(contextHolders, false, DirectionAwareContextSerializer.Direction.Outbound); } /** * Serializes all bi-directional contexts present in the passed {@code contextsContainer} that were updated * in the current application scope. This also adds a index * key for the name of all the headers added corresponding to the contained contexts. See * {@link ContextSerializationHelper} for serialization format. * * @param contextHolders {@link ContextHolder} instances to be serialized. * * @return Immutable map of key name against the serialized contexts. Empty map if no modified bi-directional * contexts are present. */ static Map getSerializedModifiedBidirectionalCtx( @SuppressWarnings("rawtypes") Collection contextHolders) throws ContextSerializationException { return getSerializedHeader(contextHolders, true, DirectionAwareContextSerializer.Direction.Inbound); } /** * De-serializes the context contained in the passed {@code contextHolder}. This method will use the context * value if de-serialized before. In such a case, the passed serializer will be ignored.

* In case, there context is not de-serialized before, the string value will be de-serialized using the metadata * associated with {@link ContextHolder#getRawSerializedForm()}. See * {@link ContextSerializationHelper} for serialization format.

* The serializer specified in the metadata will be initialized using reflection, if the passed serializer is * {@code null} * * @param contextHolder Context holder containing the context data. * @param serializer Serializer to be used if the serializer associated with the metadata is to be ignored. * * @param direction The direction of message flow for this deserialization. * @return The context object. * * @throws ContextSerializationException If the de-serialization failed. */ static T deserialize(ContextHolder contextHolder, ContextSerializer serializer, DirectionAwareContextSerializer.Direction direction) throws ContextSerializationException { if (contextHolder.hasContext()) { return contextHolder.getContext(); } if (contextHolder.isRaw()) { unwrapSerializationMetadata(contextHolder); // updates the context. } DirectionAwareContextSerializer directionAwareContextSerializer; if (null == serializer) { directionAwareContextSerializer = contextHolder.getSerializer(); } else { directionAwareContextSerializer = ContextHolder.unifySerializerTypes(serializer); } T deserialized = directionAwareContextSerializer.deserialize(contextHolder.getSerialized(), direction, contextHolder.getSerializedVersion()); if (null == deserialized) { throw new ContextSerializationException("Deserializer for context name: " + contextHolder.getContextName() + " returned null."); } contextHolder.update(deserialized); // caching the deserialized form return deserialized; } /** * Reads the contexts, if present, in the passed http request. The context key names are * retrieved by names specified in the key {@link #ALL_CONTEXTS_NAMES_KEY_NAME} separated by * {@link ContextSerializationHelper#CONTEXT_HEADER_NAMES_SEPARATOR}

* Neither the serializers will be instantiated nor the contexts will be de-serialized at this point. All * de-serialization related tasks will be done lazily when the context is required. * * @param contextKeySupplier {@link ContextKeySupplier} to provide the context keys and values. * * @return Map of context names against {@link ContextHolder}s */ @SuppressWarnings("rawtypes") static Map readContexts(ContextKeySupplier contextKeySupplier) { // Some unit tests pass the request as null. if (null == contextKeySupplier) { return Collections.emptyMap(); } return readContexts(contextKeySupplier, false); } /** * Reads the bi-directional contexts, if present, from the passed headers map. The context key names are * retrieved by names specified in the key {@link #ALL_CONTEXTS_NAMES_KEY_NAME} separated by * {@link ContextSerializationHelper#CONTEXT_HEADER_NAMES_SEPARATOR}

* * @param keySupplier {@link ContextKeySupplier} to provide the context keys and values. * * @return Map of context names against {@link ContextHolder}s. Empty map if none present. */ @SuppressWarnings({"unchecked", "rawtypes"}) static Map readBidirectionalContexts(ContextKeySupplier keySupplier) { if (null == keySupplier) { return Collections.emptyMap(); } return readContexts(keySupplier, true); } @SuppressWarnings("rawtypes") private static Map readContexts(ContextKeySupplier keySupplier, boolean bidirectional) { String allHeaderNames = keySupplier.getContextValue(ALL_CONTEXTS_NAMES_KEY_NAME); if (null == allHeaderNames || allHeaderNames.isEmpty()) { return Collections.emptyMap(); } String[] headerNames = CONTEXT_HEADER_NAMES_SEPARATOR_PATTERN.split(allHeaderNames); Map toReturn = new HashMap(headerNames.length); for (String headerName : headerNames) { String rawSerializedForm = keySupplier.getContextValue(headerName); if (null == rawSerializedForm) { // All names key is malformed for any reason continue; } String contextName = getContextNameFromHeaderName(headerName); if (null == contextName) { // Header name is malformed in all names key. continue; } toReturn.put(contextName, new ContextHolder(contextName, rawSerializedForm, bidirectional)); } return toReturn; } private static String getContextNameFromHeaderName(String headerName) { if(headerName.length() <= CONTEXT_PROTOCOL_KEY_NAME_PREFIX.length()) { return null; } return headerName.substring(CONTEXT_PROTOCOL_KEY_NAME_PREFIX.length()); } @SuppressWarnings("rawtypes") private static String constructHeaderName(ContextHolder contextHolder) { return CONTEXT_PROTOCOL_KEY_NAME_PREFIX + contextHolder.getContextName(); } private static void unwrapSerializationMetadata(ContextHolder holder) throws ContextSerializationException { String rawSerializedForm = holder.getRawSerializedForm(); String[] properties = CONTEXT_SERIALIZATION_PROPS_SEPARATOR_PATTERN.split(rawSerializedForm); if(properties.length < CONTEXT_SERIALIZATION_PROPS_COUNT) { throw new ContextSerializationException( "Invalid serialized context key format. Number of properties in the serialized form: " + properties.length + " is less than expected: " + CONTEXT_SERIALIZATION_PROPS_COUNT); } String data = properties[CONTEXT_SERIALIZATION_PROPS_DATA_INDEX]; if (properties.length > CONTEXT_SERIALIZATION_PROPS_COUNT) { // Serialized data has the separator, so we should concat them again StringBuilder dataBuilder = new StringBuilder(); for (int i = CONTEXT_SERIALIZATION_PROPS_DATA_INDEX; i < properties.length; i++) { String fragment = properties[i]; if (dataBuilder.length() > 0) { dataBuilder.append(CONTEXT_SERIALIZATION_PROPS_SEPARATOR); } dataBuilder.append(fragment); } data = dataBuilder.toString(); } else { data = unmaskIfEmpty(data); } int version; try { version = Integer.parseInt(properties[CONTEXT_SERIALIZATION_PROPS_VERSION_INDEX]); } catch (NumberFormatException e) { throw new ContextSerializationException( "Illegal serialization version: " + properties[CONTEXT_SERIALIZATION_PROPS_VERSION_INDEX], e); } holder.update(data, version, properties[CONTEXT_SERIALIZATION_PROPS_SERIALIZER_INDEX]); } @SuppressWarnings({"unchecked", "rawtypes"}) private static String _serialize(ContextHolder contextHolder, DirectionAwareContextSerializer.Direction direction) throws ContextSerializationException { if(contextHolder.hasRawSerializedForm()) { return contextHolder.getRawSerializedForm(); } DirectionAwareContextSerializer serializer = contextHolder.getSerializer(); String data = contextHolder.hasSerialized() ? contextHolder.getSerialized() : serializer.serialize(contextHolder.getContext(), direction); if (null == data) { throw new ContextSerializationException("Serializer returned null for context name: " + contextHolder.getContextName()); } if (data.isEmpty()) { data = maskEmptyString(); } StringBuilder builder = new StringBuilder(); builder.append(SERIALIZATION_FORMAT_VER); builder.append(CONTEXT_SERIALIZATION_PROPS_SEPARATOR); builder.append(contextHolder.getSerializerClassName()); builder.append(CONTEXT_SERIALIZATION_PROPS_SEPARATOR); builder.append(serializer.getVersion()); builder.append(CONTEXT_SERIALIZATION_PROPS_SEPARATOR); builder.append(data); return builder.toString(); } private static Map getSerializedHeader(@SuppressWarnings("rawtypes") Collection contextHolders, boolean onlyBidirectionalUpdatedExternally, DirectionAwareContextSerializer.Direction direction) throws ContextSerializationException { StringBuilder allContextHeaderNames = new StringBuilder(); Map toReturn = new HashMap(contextHolders.size()); for (@SuppressWarnings("rawtypes") ContextHolder contextHolder : contextHolders) { if (onlyBidirectionalUpdatedExternally && !contextHolder.shouldFlowBackInResponse()) { continue; } String headerName = constructHeaderName(contextHolder); if (allContextHeaderNames.length() > 0) { allContextHeaderNames.append(CONTEXT_HEADER_NAMES_SEPARATOR); } allContextHeaderNames.append(headerName); toReturn.put(headerName, _serialize(contextHolder, direction)); } if(!toReturn.isEmpty()) { toReturn.put(ALL_CONTEXTS_NAMES_KEY_NAME, allContextHeaderNames.toString()); } return Collections.unmodifiableMap(toReturn); } private static String maskEmptyString() { return EMPTY_SERIALIZED_DATA; } private static String unmaskIfEmpty(String serialized) { return EMPTY_SERIALIZED_DATA.equals(serialized) ? "" : serialized; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy