org.forgerock.openam.cts.utils.blob.strategies.AttributeCompressionStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openam.cts.utils.blob.strategies.AttributeCompressionStrategy.java

Source

/**
 * Copyright 2013 ForgeRock AS.
 *
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 */
package org.forgerock.openam.cts.utils.blob.strategies;

import javax.inject.Inject;
import com.iplanet.dpro.session.service.InternalSession;
import org.forgerock.openam.cts.api.TokenType;
import org.forgerock.openam.cts.api.tokens.Token;
import org.forgerock.openam.cts.utils.JSONSerialisation;
import org.forgerock.openam.cts.utils.blob.BlobStrategy;
import org.forgerock.openam.cts.utils.blob.TokenBlobUtils;
import org.forgerock.openam.cts.utils.blob.TokenStrategyFailedException;
import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Responsible for performing a specialised JSON compression based on the
 * attribute name being stored in the JSON.
 *
 * The compression is a somewhat simple reduction of each Attribute Name
 * to its initials. This works because we know the fields within the
 * InternalSession.
 *
 * This approach is however brittle and only recommended if it will make
 * the required difference in performance.
 *
 * @author robert.wapshott@forgerock.com
 */
public class AttributeCompressionStrategy implements BlobStrategy {
    // Injected
    private final TokenBlobUtils blobUtils;

    private final BidiMap replacement = new DualHashBidiMap();

    @Inject
    public AttributeCompressionStrategy(TokenBlobUtils blobUtils) {
        this.blobUtils = blobUtils;

        for (Field f : getAllValidFields(InternalSession.class)) {
            String fieldName = f.getName();
            replacement.put(fieldName, getInitials(fieldName));
        }
    }

    /**
     * Ensures the Token is a Session Token and performs the compression.
     * @param token {@inheritDoc}
     * @throws org.forgerock.openam.cts.utils.blob.TokenStrategyFailedException {@inheritDoc}
     */
    public void perform(Token token) throws TokenStrategyFailedException {
        if (!isTokenValidForCompression(token)) {
            return;
        }

        performUpdate(token, replacement);
    }

    /**
     * Ensures the Token is a Session Token and reverses the compression.
     * @param token {@inheritDoc}
     * @throws TokenStrategyFailedException {@inheritDoc}
     */
    public void reverse(Token token) throws TokenStrategyFailedException {
        if (!isTokenValidForCompression(token)) {
            return;
        }

        performUpdate(token, replacement.inverseBidiMap());
    }

    /**
     * A simple heuristic for determining the Token is a Session Token containing JSON.
     *
     * @param token Non null Token to examine.
     * @return True if it starts and ends with curly brackets.
     */
    private boolean isTokenValidForCompression(Token token) {
        if (token.getType() != TokenType.SESSION) {
            return false;
        }

        if (token.getBlob() == null) {
            return false;
        }

        String contents = blobUtils.getBlobAsString(token);
        return contents.startsWith("{") && contents.endsWith("}");
    }

    /**
     * Update the Token with the compressed contents.
     * @param token Non null
     * @param map Non null
     * @throws TokenStrategyFailedException If any error occured.
     */
    private void performUpdate(Token token, BidiMap map) throws TokenStrategyFailedException {
        String contents = blobUtils.getBlobAsString(token);
        String replace = applyReplacement(map, contents);
        blobUtils.setBlobFromString(token, replace);
    }

    /**
     * Perform the keyword substitution.
     *
     * @param replacement A mapping of one value to another to perform.
     * @param contents String to modify.
     */
    private String applyReplacement(final BidiMap replacement, String contents) {
        for (Object key : replacement.keySet()) {
            String attributeName = (String) key;
            String attributeInitials = (String) replacement.get(key);
            contents = contents.replaceAll(JSONSerialisation.jsonAttributeName(attributeName),
                    JSONSerialisation.jsonAttributeName(attributeInitials));
        }
        return contents;
    }

    /**
     * Generate the initials from the given name.
     *
     * Note: This function is intended to operate against Java field name syntax.
     * As such it simply picks out the first character and all subsequent upper
     * case characters from the String.
     *
     * @param name Non null string that follows Java field name syntax.
     * @return The initials of the field.
     */
    public static String getInitials(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name was null");
        }
        StringBuilder r = new StringBuilder();
        boolean start = true;
        for (int ii = 0; ii < name.length(); ii++) {
            char c = name.charAt(ii);
            if (start) {
                r.append(c);
                start = false;
                continue;
            }
            if (Character.isUpperCase(c)) {
                r.append(c);
            }
        }
        return r.toString();
    }

    /**
     * Examines the class using reflection for all declared fields which are suitable
     * for serialisation.
     *
     * @param c Non null class to examine.
     *
     * @return A non null but possibly empty collection of Fields.
     */
    public static Collection<Field> getAllValidFields(Class c) {
        List<Field> r = new ArrayList<Field>();
        for (Field f : c.getDeclaredFields()) {
            int modifiers = f.getModifiers();
            if (Modifier.isStatic(modifiers))
                continue;
            if (Modifier.isVolatile(modifiers))
                continue;
            if (Modifier.isTransient(modifiers))
                continue;
            r.add(f);
        }
        return r;
    }
}