package com.atlassian.stash.internal.hibernate;

import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;

import java.io.Serializable;

/**
 * Hibernate interceptor that guards against any empty strings being stored in the database. Empty strings are
 * problematic because Oracle treats the empty string (zero-length) as {@code NULL}, which can cause all kinds
 * of misery if the column in the database is {@code NOT NULL}, or if the application treats {@code null}s and
 * empty values differently.
 * <p>
 * To prevent such problems, this interceptor simply does not allow storing empty strings at all. All Hibernate
 * entities should ensure that no empty strings are allowed, either through the use of JSR-303 validations or
 * {@code Preconditions}-style checks. This interceptor is a catch-all for any empty strings which escape such
 * validation, but the error messages are less helpful than the JSR-303 style messages.
 */
public class NoEmptyStringsInterceptor extends EmptyInterceptor {

    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
                                String[] propertyNames, Type[] types) {
        checkStrings(entity, id, currentState, propertyNames);

        return false;
    }

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        checkStrings(entity, id, state, propertyNames);

        return false;
    }

    private static void checkStrings(Object entity, Serializable id, Object[] state, String[] propertyNames) {
        StringBuilder errors = null;
        String packageName = entity.getClass().getPackage().getName();
        if (packageName.startsWith("com.atlassian.stash") || packageName.startsWith("com.atlassian.bitbucket")) {
            // only apply the check to Stash entities. Crowd can occasionally write empty strings, but can handle the
            // Oracle quirks.
            for (int i = 0; i < state.length; ++i) {
                if ("".equals(state[i])) {
                    if (errors == null) {
                        errors = new StringBuilder()
                                .append(entity.getClass().getName())
                                .append(" with id '")
                                .append(id)
                                .append("' has empty string properties: [");
                    } else {
                        errors.append(", ");
                    }
                    errors.append(propertyNames[i]);
                }
            }
        }

        if (errors != null) {
            errors.append("]");
            throw new CallbackException(errors.toString());
        }
    }
}
