0
\$\begingroup\$

Please share your comments and better ideas, to print java graph, which manipulates selected (@Mask) fields as XX, thus avoid logging confidential data, through this tool.

GitHub

public class MaskLogger {
    private static final String SERIAL_VERSION_UID = "serialVersionUID";
    private static final String ELEMENT_DATA = "elementData";
    private static final String JAVA_LANG = "java.lang";
    private final static String EQAULS = "=";
    private final static String SQ_BRACKET_OPEN = "[";
    private final static String SQ_BRACKET_CLOSE = "]";
    private final static String BRACE_OPEN = "(";
    private final static String BRACE_CLOSE = ")";
    private final static String COMMA = ", ";
    private final static String STRING = "java.lang.String";

    public <T> String toString(T instance) {
        StringBuilder result = new StringBuilder(100);
        result.append(SQ_BRACKET_OPEN);
        Field[] fields = instance.getClass().getDeclaredFields();
        int noOfFields = fields.length;
        for (Field field : fields) {
            noOfFields--;
            field.setAccessible(true);
            if (excludeField(instance, field)) {
                continue;
            }
            try {
                if (!needDeepDive(field)) {
                    result.append(field.getName());
                    result.append(EQAULS);
                    if (field.isAnnotationPresent(Mask.class)) {
                        result.append("XX");
                    } else {
                        result.append(field.get(instance));
                    }
                    if (noOfFields != 0) {
                        result.append(COMMA);
                    }
                } else if (Collection.class.isAssignableFrom(field.getType())) {
                    Collection<?> collection = (Collection<?>) field.get(instance);
                    result.append(field.getName());
                    result.append(EQAULS);
                    result.append(SQ_BRACKET_OPEN);
                    for (Object object : collection) {
                        result.append(toString(object));
                    }
                    result.append(SQ_BRACKET_CLOSE + COMMA);
                } else if (field.getType().isArray()) {
                    Object[] collection = (Object[]) field.get(instance);
                    result.append(field.getName());
                    result.append(EQAULS);
                    result.append(BRACE_OPEN);
                    int noOfStringFields = collection.length;
                    for (Object object : collection) {
                        noOfStringFields--;
                        if (object == null && noOfStringFields == 0) {
                            result.delete(result.length() - 1, result.length());
                        } else if (object == null) {
                            continue;
                        } else if (object.getClass().getCanonicalName().equals(STRING)) {
                            result.append(object.toString());
                            result.append(COMMA);
                        } else {
                            result.append(toString(object));
                        }
                    }
                    result.deleteCharAt(result.length() - 1);
                    result.append(BRACE_CLOSE);
                } else {
                    result.append(toString(field.get(instance)));
                }
            } catch (IllegalAccessException e) {
                return "Error occurred while masking input object:" + e.getMessage();
            }

        }
        result.append(SQ_BRACKET_CLOSE);
        return result.toString();
    }

    /**
     * It excludes the unnecessary fields in the object graph.
     *
     * @param instance
     * @param field
     * @return true/false
     */
    private <T> boolean excludeField(T instance, Field field) {
        if ((instance instanceof Collection<?> && !field.getName().equalsIgnoreCase(ELEMENT_DATA))
                || field.getName().equalsIgnoreCase(SERIAL_VERSION_UID)) {
            return true;
        }
        return false;
    }

    /**
     * It checks whether needs to dive deep into the object graph to scan
     * through the @Mask annotation
     *
     * @param field
     * @return true/false
     */
    private boolean needDeepDive(Field field) {
        boolean result = true;
        if (field.isAnnotationPresent(Mask.class)) {
            result = false;
        } else if (!field.getType().isArray()
                && (field.getType().getCanonicalName().startsWith(JAVA_LANG) || field.getType().isPrimitive())) {
            result = false;
        }
        return result;
    }
}

package com.immibytes.util.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD })
public @interface Mask {

}
\$\endgroup\$
1
\$\begingroup\$

You can implement it using one line of code with simple and opensource java object layout tool:

public <T> String toString(T instance) {
    return GraphLayout.parseInstance(instance).toPrintable();
}

that tool also can show the memory footprint of given object with all its fields.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ But, the requirement is to intercept each field or object while printing the graph. The aim is to check the availability of @Mask annotation and manipulate the result. \$\endgroup\$ – Senthil Arumugam SP Oct 19 '15 at 21:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.