Package yalp.test

Source Code of yalp.test.Fixtures

package yalp.test;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.scanner.ScannerException;
import yalp.Logger;
import yalp.Yalp;
import yalp.classloading.ApplicationClasses;
import yalp.data.binding.Binder;
import yalp.data.binding.ParamNode;
import yalp.data.binding.RootParamNode;
import yalp.data.binding.types.DateBinder;
import yalp.db.DB;
import yalp.db.DBPlugin;
import yalp.db.SQLSplitter;
import yalp.db.Model;
import yalp.db.jpa.JPAPlugin;
import yalp.exceptions.DatabaseException;
import yalp.exceptions.UnexpectedException;
import yalp.exceptions.YAMLException;
import yalp.libs.IO;
import yalp.templates.TemplateLoader;
import yalp.vfs.VirtualFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.Entity;

public class Fixtures {

    static Pattern keyPattern = Pattern.compile("([^(]+)\\(([^)]+)\\)");
    // Allows people to clear the cache, so Fixture is not stateful
    public static Map<String, Object> idCache = new HashMap<String, Object>();

    public static void executeSQL(String sqlScript) {
        for (CharSequence sql : new SQLSplitter(sqlScript)) {
            final String s = sql.toString().trim();
            if (s.length() > 0) {
                DB.execute(s);
            }
        }
    }

    public static void executeSQL(File sqlScript) {
        executeSQL(IO.readContentAsString(sqlScript));
    }

    /**
     * Delete all Model instances for the given types using the underlying persistence mechanisms
     *
     * @param types Types to delete
     */
    public static void delete(Class<? extends Model>... types) {
        idCache.clear();
        disableForeignKeyConstraints();
        for (Class<? extends Model> type : types) {
            try {
                Model.Manager.factoryFor(type).deleteAll();
            } catch (Exception e) {
                Logger.error(e, "While deleting " + type + " instances");
            }

        }
        enableForeignKeyConstraints();
        Yalp.pluginCollection.afterFixtureLoad();
    }

    /**
     * Delete all Model instances for the given types using the underlying persistence mechanisms
     *
     * @param types Types to delete
     */
    public static void delete(List<Class<? extends Model>> classes) {
        @SuppressWarnings("unchecked")
        Class<? extends Model>[] types = new Class[classes.size()];
        for (int i = 0; i < types.length; i++) {
            types[i] = classes.get(i);
        }
        delete(types);
    }

    /**
     * Delete all Model instances for the all available types using the underlying persistence mechanisms
     */
    @SuppressWarnings("unchecked")
    public static void deleteAllModels() {
        List<Class<? extends Model>> classes = new ArrayList<Class<? extends Model>>();
        for (ApplicationClasses.ApplicationClass c : Yalp.classes.getAssignableClasses(Model.class)) {
            if (c.javaClass.isAnnotationPresent(Entity.class)) {
                classes.add((Class<? extends Model>) c.javaClass);
            }
        }
        disableForeignKeyConstraints();
        Fixtures.delete(classes);
    }

    /**
     * Use deleteDatabase() instead
     *
     * @deprecated use {@link deleteDatabase()} instead
     */
    @Deprecated
    public static void deleteAll() {
        deleteDatabase();
    }

    static String[] dontDeleteTheseTables = new String[]{"yalp_evolutions"};

    /**
     * Flush the entire JDBC database
     */
    public static void deleteDatabase() {
        try {
            idCache.clear();
            List<String> names = new ArrayList<String>();
            ResultSet rs = DB.getConnection().getMetaData().getTables(null, null, null, new String[]{"TABLE"});
            while (rs.next()) {
                String name = rs.getString("TABLE_NAME");
                names.add(name);
            }
            disableForeignKeyConstraints();
            for (String name : names) {
                if (Arrays.binarySearch(dontDeleteTheseTables, name) < 0) {
                    if (Logger.isTraceEnabled()) {
                        Logger.trace("Dropping content of table %s", name);
                    }
                    DB.execute(getDeleteTableStmt(name) + ";");
                }
            }
            enableForeignKeyConstraints();
            Yalp.pluginCollection.afterFixtureLoad();
        } catch (Exception e) {
            throw new RuntimeException("Cannot delete all table data : " + e.getMessage(), e);
        }
    }

    /**
     * @param name
     * @deprecated use {@link loadModels(String...)} instead
     */
    @Deprecated
    public static void load(String name) {
        loadModels(name);
    }

    /**
     * Load Model instances from a YAML file and persist them using the underlying persistence mechanism.
     * The format of the YAML file is constrained, see the Fixtures manual page
     *
     * @param name Name of a YAML file somewhere in the classpath (or conf/)
     */
    public static void loadModels(String name) {
        VirtualFile yamlFile = null;
        try {
            for (VirtualFile vf : Yalp.javaPath) {
                yamlFile = vf.child(name);
                if (yamlFile != null && yamlFile.exists()) {
                    break;
                }
            }
            if (yamlFile == null || !yamlFile.exists()) {
                throw new RuntimeException("Cannot load fixture " + name + ", the file was not found");
            }

            String renderedYaml = TemplateLoader.load(yamlFile).render();

            Yaml yaml = new Yaml();
            Object o = yaml.load(renderedYaml);
            if (o instanceof LinkedHashMap<?, ?>) {
                @SuppressWarnings("unchecked") LinkedHashMap<Object, Map<?, ?>> objects = (LinkedHashMap<Object, Map<?, ?>>) o;
                for (Object key : objects.keySet()) {
                    Matcher matcher = keyPattern.matcher(key.toString().trim());
                    if (matcher.matches()) {
                        // Type of the object. i.e. models.employee
                        String type = matcher.group(1);
                        // Id of the entity i.e. nicolas
                        String id = matcher.group(2);
                        if (!type.startsWith("models.")) {
                            type = "models." + type;
                        }

                        // Was the entity already defined?
                        if (idCache.containsKey(type + "-" + id)) {
                            throw new RuntimeException("Cannot load fixture " + name + ", duplicate id '" + id + "' for type " + type);
                        }


                        // Those are the properties that were parsed from the YML file
                        final Map<?, ?> entityValues = objects.get(key);

                        // Prefix is object, why is that?
                        final Map<String, String[]> fields = serialize(entityValues, "object");


                        @SuppressWarnings("unchecked")
                        Class<Model> cType = (Class<Model>) Yalp.classloader.loadClass(type);
                        final Map<String, String[]> resolvedFields = resolveDependencies(cType, fields);

                        RootParamNode rootParamNode = ParamNode.convert(resolvedFields);
                        // This is kind of hacky. This basically says that if we have an embedded class we should ignore it.
                        if (Model.class.isAssignableFrom(cType)) {

                            Model model = (Model) Binder.bind(rootParamNode, "object", cType, cType, null);
                            for (Field f : model.getClass().getFields()) {
                                if (f.getType().isAssignableFrom(Map.class)) {
                                    f.set(model, objects.get(key).get(f.getName()));
                                }
                                if (f.getType().equals(byte[].class)) {
                                    f.set(model, objects.get(key).get(f.getName()));
                                }
                            }
                            model._save();

                            Class<?> tType = cType;
                            while (!tType.equals(Object.class)) {
                                idCache.put(tType.getName() + "-" + id, Model.Manager.factoryFor(cType).keyValue((Model) model));
                                tType = tType.getSuperclass();
                            }
                        } else {
                            idCache.put(cType.getName() + "-" + id, Binder.bind(rootParamNode, "object", cType, cType, null));
                        }
                    }
                }
            }
            // Most persistence engine will need to clear their state
            Yalp.pluginCollection.afterFixtureLoad();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class " + e.getMessage() + " was not found", e);
        } catch (ScannerException e) {
            throw new YAMLException(e, yamlFile);
        } catch (Throwable e) {
            throw new RuntimeException("Cannot load fixture " + name + ": " + e.getMessage(), e);
        }
    }

    /**
     * @deprecated use {@link loadModels(String...)} instead
     */
    @Deprecated
    public static void load(String... names) {
        for (String name : names) {
            loadModels(name);
        }
    }

    /**
     * @see loadModels(String name)
     */
    public static void loadModels(String... names) {
        for (String name : names) {
            loadModels(name);
        }
    }

    /**
     * @deprecated use {@link loadModels(String...)} instead
     */
    public static void load(List<String> names) {
        loadModels(names);
    }

    /**
     * @see loadModels(String name)
     */
    public static void loadModels(List<String> names) {
        String[] tNames = new String[names.size()];
        for (int i = 0; i < tNames.length; i++) {
            tNames[i] = names.get(i);
        }
        load(tNames);
    }

    /**
     * Load and parse a plain YAML file and returns the corresponding Java objects.
     * The YAML parser used is SnakeYAML (http://code.google.com/p/snakeyaml/)
     *
     * @param name Name of a YAML file somewhere in the classpath (or conf/)me
     * @return Java objects
     */
    public static Object loadYaml(String name) {
        return loadYaml(name, Object.class);
    }

    /**
     * Load and parse a plain YAML file and returns the corresponding Java List.
     * The YAML parser used is SnakeYAML (http://code.google.com/p/snakeyaml/)
     *
     * @param name Name of a YAML file somewhere in the classpath (or conf/)me
     * @return Java List representing the YAML data
     */
    public static List<?> loadYamlAsList(String name) {
        return (List<?>) loadYaml(name);
    }

    /**
     * Load and parse a plain YAML file and returns the corresponding Java Map.
     * The YAML parser used is SnakeYAML (http://code.google.com/p/snakeyaml/)
     *
     * @param name Name of a YAML file somewhere in the classpath (or conf/)me
     * @return Java Map representing the YAML data
     */
    public static Map<?, ?> loadYamlAsMap(String name) {
        return (Map<?, ?>) loadYaml(name);
    }

    /**
     * Load and parse a plain YAML file and returns the corresponding Java Map.
     * The YAML parser used is SnakeYAML (http://code.google.com/p/snakeyaml/)
     *
     * @param name  Name of a YAML file somewhere in the classpath (or conf/)me
     * @param clazz the expected class
     * @return Object representing the YAML data
     */
    @SuppressWarnings("unchecked")
    public static <T> T loadYaml(String name, Class<T> clazz) {
        Yaml yaml = new Yaml(new CustomClassLoaderConstructor(clazz, Yalp.classloader));
        yaml.setBeanAccess(BeanAccess.FIELD);
        return (T) loadYaml(name, yaml);
    }

    @SuppressWarnings("unchecked")
    public static <T> T loadYaml(String name, Yaml yaml) {
        VirtualFile yamlFile = null;
        try {
            for (VirtualFile vf : Yalp.javaPath) {
                yamlFile = vf.child(name);
                if (yamlFile != null && yamlFile.exists()) {
                    break;
                }
            }
            InputStream is = Yalp.classloader.getResourceAsStream(name);
            if (is == null) {
                throw new RuntimeException("Cannot load fixture " + name + ", the file was not found");
            }
            Object o = yaml.load(is);
            return (T) o;
        } catch (ScannerException e) {
            throw new YAMLException(e, yamlFile);
        } catch (Throwable e) {
            throw new RuntimeException("Cannot load fixture " + name + ": " + e.getMessage(), e);
        }
    }


    /**
     * Delete a directory recursively
     *
     * @param path relative path of the directory to delete
     */
    public static void deleteDirectory(String path) {
        try {
            FileUtils.deleteDirectory(Yalp.getFile(path));
        } catch (IOException ex) {
            throw new UnexpectedException(ex);
        }
    }

    // Private


    /**
     * TODO: reuse beanutils or MapUtils?
     *
     * @param entityProperties
     * @param prefix
     * @return an hash with the resolved entity name and the corresponding value
     */
    static Map<String, String[]> serialize(Map<?, ?> entityProperties, String prefix) {

        if (entityProperties == null) {
            return MapUtils.EMPTY_MAP;
        }

        final Map<String, String[]> serialized = new HashMap<String, String[]>();

        for (Object key : entityProperties.keySet()) {

            Object value = entityProperties.get(key);
            if (value == null) {
                continue;
            }
            if (value instanceof Map<?, ?>) {
                serialized.putAll(serialize((Map<?, ?>) value, prefix + "[" + key.toString() + "]"));
            } else if (value instanceof Date) {
                serialized.put(prefix + "." + key.toString(), new String[]{new SimpleDateFormat(DateBinder.ISO8601).format(((Date) value))});
            } else if (Collection.class.isAssignableFrom(value.getClass())) {
                Collection<?> l = (Collection<?>) value;
                String[] r = new String[l.size()];
                int i = 0;
                for (Object el : l) {
                    r[i++] = el.toString();
                }
                serialized.put(prefix + "." + key.toString(), r);
            } else if (value instanceof String && value.toString().matches("<<<\\s*\\{[^}]+}\\s*")) {
                Matcher m = Pattern.compile("<<<\\s*\\{([^}]+)}\\s*").matcher(value.toString());
                m.find();
                String file = m.group(1);
                VirtualFile f = Yalp.getVirtualFile(file);
                if (f != null && f.exists()) {
                    serialized.put(prefix + "." + key.toString(), new String[]{f.contentAsString()});
                }
            } else {
                serialized.put(prefix + "." + key.toString(), new String[]{value.toString()});
            }
        }

        return serialized;
    }

    @SuppressWarnings("unchecked")
    /**
     *  Resolve dependencies between objects using their keys. For each referenced objects, it sets the foreign key
     */
    static Map<String, String[]> resolveDependencies(Class<Model> type, Map<String, String[]> yml) {

        // Contains all the fields (object properties) we should look up
        final Set<Field> fields = new HashSet<Field>();
        final Map<String, String[]> resolvedYml = new HashMap<String, String[]>();
        resolvedYml.putAll(yml);

        // Look up the super classes
        Class<?> clazz = type;
        while (!clazz.equals(Object.class)) {
            Collections.addAll(fields, clazz.getDeclaredFields());
            clazz = clazz.getSuperclass();
        }


        // Iterate through the Entity property list
        // @Embedded are not managed by the JPA plugin
        // This is not the nicest way of doing things.
        //modelFields =  Model.Manager.factoryFor(type).listProperties();
        final List<Model.Property> modelFields = new JPAPlugin.JPAModelLoader(type).listProperties();

        for (Model.Property field : modelFields) {
            // If we have a relation, get the matching object
            if (field.isRelation) {
                // These are the Ids that were set in the yml file (i.e person(nicolas)-> nicolas is the id)
                final String[] ids = resolvedYml.get("object." + field.name);
                if (ids != null) {
                    final String[] resolvedIds = new String[ids.length];
                    for (int i = 0; i < ids.length; i++) {
                        final String id = field.relationType.getName() + "-" + ids[i];
                        if (!idCache.containsKey(id)) {
                            throw new RuntimeException("No previous reference found for object of type " + field.name + " with key " + ids[i]);
                        }
                        // We now get the primary key
                        resolvedIds[i] = idCache.get(id).toString();
                    }
                    // Set the primary keys instead of the object itself.
                    // Model.Manager.factoryFor((Class<? extends Model>)field.relationType).keyName() returns the primary key label.
                    if (Model.class.isAssignableFrom(field.relationType)) {
                        resolvedYml.put("object." + field.name + "." + Model.Manager.factoryFor((Class<? extends Model>) field.relationType).keyName(), resolvedIds);
                    } else {
                        // Might be an embedded object
                        final String id = field.relationType.getName() + "-" + ids[0];
                        Object o = idCache.get(id);
                        // This can be a composite key
                        if (o.getClass().isArray()) {
                            for (Object a : (Object[]) o) {
                                for (Field f : field.relationType.getDeclaredFields()) {
                                    try {
                                        resolvedYml.put("object." + field.name + "." + f.getName(), new String[]{f.get(a).toString()});
                                    } catch (Exception e) {
                                        // Ignores
                                    }
                                }
                            }
                        } else {
                            for (Field f : field.relationType.getDeclaredFields()) {
                                try {
                                    resolvedYml.put("object." + field.name + "." + f.getName(), new String[]{f.get(o).toString()});
                                } catch (Exception e) {
                                    // Ignores
                                }
                            }
                        }
                    }
                }

                resolvedYml.remove("object." + field.name);
            }
        }
        // Returns the map containing the ids to load for this object's relation.
        return resolvedYml;
    }

    private static void disableForeignKeyConstraints() {
        if (DBPlugin.url.startsWith("jdbc:oracle:")) {
            DB.execute("begin\n"
                    + "for i in (select constraint_name, table_name from user_constraints where constraint_type ='R'\n"
                    + "and status = 'ENABLED') LOOP\n"
                    + "execute immediate 'alter table '||i.table_name||' disable constraint '||i.constraint_name||'';\n"
                    + "end loop;\n"
                    + "end;"
            );
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:hsqldb:")) {
            DB.execute("SET REFERENTIAL_INTEGRITY FALSE");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:h2:")) {
            DB.execute("SET REFERENTIAL_INTEGRITY FALSE");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:mysql:")) {
            DB.execute("SET foreign_key_checks = 0;");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:postgresql:")) {
            DB.execute("SET CONSTRAINTS ALL DEFERRED");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:sqlserver:")) {
            Statement exec = null;

            try {
                List<String> names = new ArrayList<String>();
                Connection connection = DB.getConnection();

                ResultSet rs = connection.getMetaData().getTables(null, null, null, new String[]{"TABLE"});
                while (rs.next()) {
                    String name = rs.getString("TABLE_NAME");
                    names.add(name);
                }

                // Then we disable all foreign keys
                exec = connection.createStatement();
                for (String tableName : names)
                    exec.addBatch("ALTER TABLE " + tableName + " NOCHECK CONSTRAINT ALL");
                exec.executeBatch();
                exec.close();

                return;
            } catch (SQLException ex) {
                throw new DatabaseException("Error while disabling foreign keys", ex);
            }
        }

        // Maybe Log a WARN for unsupported DB ?
        Logger.warn("Fixtures : unable to disable constraints, unsupported database : " + DBPlugin.url);
    }

    private static void enableForeignKeyConstraints() {
        if (DBPlugin.url.startsWith("jdbc:oracle:")) {
            DB.execute("begin\n"
                    + "for i in (select constraint_name, table_name from user_constraints where constraint_type ='R'\n"
                    + "and status = 'DISABLED') LOOP\n"
                    + "execute immediate 'alter table '||i.table_name||' enable constraint '||i.constraint_name||'';\n"
                    + "end loop;\n"
                    + "end;"
            );
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:hsqldb:")) {
            DB.execute("SET REFERENTIAL_INTEGRITY TRUE");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:h2:")) {
            DB.execute("SET REFERENTIAL_INTEGRITY TRUE");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:mysql:")) {
            DB.execute("SET foreign_key_checks = 1;");
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:postgresql:")) {
            return;
        }

        if (DBPlugin.url.startsWith("jdbc:sqlserver:")) {
            Connection connect = null;
            Statement exec = null;
            try {
                connect = DB.getConnection();
                // We must first drop all foreign keys
                ArrayList<String> checkFKCommands = new ArrayList<String>();
                exec = connect.createStatement();
                ResultSet rs = exec.executeQuery("SELECT 'ALTER TABLE ' + TABLE_SCHEMA + '.[' + TABLE_NAME +'] WITH CHECK CHECK CONSTRAINT [' + CONSTRAINT_NAME + ']' FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'");
                while (rs.next()) {
                    checkFKCommands.add(rs.getString(1));
                }
                exec.close();
                exec = null;

                // Now we have the drop commands, let's execute them
                exec = connect.createStatement();
                for (String sql : checkFKCommands)
                    exec.addBatch(sql);
                exec.executeBatch();
                exec.close();
            } catch (SQLException ex) {
                throw new DatabaseException("Cannot enable foreign keys", ex);
            }
            return;
        }

        Logger.warn("Fixtures : unable to enable constraints, unsupported database : " + DBPlugin.url);
    }

    static String getDeleteTableStmt(String name) {
        if (DBPlugin.url.startsWith("jdbc:mysql:")) {
            return "TRUNCATE TABLE " + name;
        } else if (DBPlugin.url.startsWith("jdbc:postgresql:")) {
            return "TRUNCATE TABLE " + name + " cascade";
        } else if (DBPlugin.url.startsWith("jdbc:oracle:")) {
            return "TRUNCATE TABLE " + name;
        }
        return "DELETE FROM " + name;
    }

}
TOP

Related Classes of yalp.test.Fixtures

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.