001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.sql.config.spring;
017
018import com.google.common.base.Optional;
019import com.google.common.collect.ImmutableList;
020import com.google.common.collect.Lists;
021import com.google.common.collect.Maps;
022import org.apache.commons.lang3.StringUtils;
023import org.kuali.common.util.metainf.model.MetaInfContext;
024import org.kuali.common.util.metainf.model.MetaInfResource;
025import org.kuali.common.util.metainf.model.MetaInfResourceLocationComparator;
026import org.kuali.common.util.metainf.service.MetaInfUtils;
027import org.kuali.common.util.metainf.spring.MetaInfConfigUtils;
028import org.kuali.common.util.metainf.spring.MetaInfContextsConfig;
029import org.kuali.common.util.metainf.spring.MetaInfDataLocation;
030import org.kuali.common.util.metainf.spring.MetaInfDataType;
031import org.kuali.common.util.metainf.spring.MetaInfExecutableConfig;
032import org.kuali.common.util.metainf.spring.MetaInfGroup;
033import org.kuali.common.util.nullify.NullUtils;
034import org.kuali.common.util.project.ProjectUtils;
035import org.kuali.common.util.project.model.Build;
036import org.kuali.common.util.project.model.Project;
037import org.kuali.common.util.project.spring.AutowiredProjectConfig;
038import org.kuali.common.util.spring.SpringUtils;
039import org.kuali.common.util.spring.env.EnvironmentService;
040import org.kuali.common.util.spring.service.SpringServiceConfig;
041import org.springframework.beans.factory.annotation.Autowired;
042import org.springframework.context.annotation.Bean;
043import org.springframework.context.annotation.Configuration;
044import org.springframework.context.annotation.Import;
045
046import java.io.File;
047import java.util.Comparator;
048import java.util.List;
049import java.util.Map;
050
051/**
052 * Defines the configuration for creating the sql property files that define how the database is created.
053 *
054 * @author Kuali Rice Team (rice.collab@kuali.org)
055 */
056@Configuration
057@Import({AutowiredProjectConfig.class, MetaInfExecutableConfig.class, SpringServiceConfig.class})
058public class RiceSqlConfig implements MetaInfContextsConfig {
059
060    private static final Boolean DEFAULT_GENERATE_RELATIVE_PATHS = Boolean.TRUE;
061    private static final String RELATIVE_KEY = MetaInfUtils.PROPERTY_PREFIX + ".sql.relative";
062    private static final String PREFIX = "sql";
063    private static final String DEFAULT_VENDORS = "mysql,oracle";
064    private static final String VENDORS_KEY = MetaInfUtils.PROPERTY_PREFIX + ".db.vendors";
065
066    // All paths must have the hardcoded separator to be consistent for deployment
067    private static final String PATH_SEPARATOR = "/";
068
069    private static final String INITIAL_SQL_PATH = "initial-sql" + PATH_SEPARATOR + "2.3.0";
070    private static final String UPGRADE_SQL_PATH = "upgrades" + PATH_SEPARATOR + "*";
071    private static final String SCHEMA_SQL_PATH = "rice-" + MetaInfGroup.SCHEMA.name().toLowerCase() + ".sql";
072    private static final String CONSTRAINTS_SQL_PATH = "rice-" + MetaInfGroup.CONSTRAINTS.name().toLowerCase() + ".sql";
073    private static final String ALL_SQL_PATH = "*.sql";
074
075    /**
076     * The Spring environment.
077     */
078    @Autowired
079    EnvironmentService env;
080
081    /**
082     * The Rice Maven project.
083     */
084    @Autowired
085    Project project;
086
087    /**
088     * The build information.
089     */
090    @Autowired
091    Build build;
092
093    /**
094     * {@inheritDoc}
095     *
096     * <p>
097     * All of the initial data (the data included in the {@code MetaInfGroup.SCHEMA}, {@code MetaInfGroup.CONSTRAINTS},
098     * or {@code MetaInfGroup.DATA} groups) needs to be added before the update data (the data included in the
099     * {@code MetaInfGroup.OTHER} group).
100     * </p>
101     */
102    @Override
103    @Bean
104    public List<MetaInfContext> metaInfContexts() {
105        List<MetaInfContext> metaInfContexts = Lists.newArrayList();
106
107        List<MetaInfDataType> types = Lists.newArrayList(MetaInfDataType.BOOTSTRAP, MetaInfDataType.DEMO, MetaInfDataType.TEST);
108        List<String> vendors = SpringUtils.getNoneSensitiveListFromCSV(env, VENDORS_KEY, DEFAULT_VENDORS);
109        List<MetaInfGroup> groups = Lists.newArrayList(MetaInfGroup.SCHEMA, MetaInfGroup.DATA, MetaInfGroup.CONSTRAINTS);
110
111        for (MetaInfDataType type : types) {
112            for (MetaInfDataLocation location : MetaInfDataLocation.values()) {
113                for (String vendor : vendors) {
114                    for (MetaInfGroup group : groups) {
115                        List<MetaInfContext> contexts = getMetaInfContexts(group, INITIAL_SQL_PATH, vendor,
116                                location, type);
117                        metaInfContexts.addAll(contexts);
118                    }
119                }
120            }
121        }
122
123        for (MetaInfDataType type : types) {
124            for (MetaInfDataLocation location : MetaInfDataLocation.values()) {
125                for (String vendor : vendors) {
126                    List<MetaInfContext> contexts = getMetaInfContexts(MetaInfGroup.OTHER, UPGRADE_SQL_PATH, vendor,
127                            location, type);
128                    metaInfContexts.addAll(contexts);
129                }
130            }
131        }
132
133        return ImmutableList.copyOf(metaInfContexts);
134    }
135
136    /**
137     * Creates a list of META-INF contexts for the given {@code group}, {@code qualifier}, {@code vendor},
138     * {@code location}, and {@code type}.
139     *
140     * @param group the group of the data to create the context for
141     * @param qualifier the prefix to add to the initial resource path
142     * @param vendor the database vendor to create the context for
143     * @param location the location of the data to create the context for
144     * @param type the type of data to create the context for
145     *
146     * @return a list of META-INF contexts
147     */
148    protected List<MetaInfContext> getMetaInfContexts(MetaInfGroup group, String qualifier, String vendor, MetaInfDataLocation location, MetaInfDataType type) {
149        List<MetaInfContext> metaInfContexts = Lists.newArrayList();
150
151        File scanDir = build.getOutputDir();
152        String encoding = build.getEncoding();
153
154        Comparator<MetaInfResource> comparator = new MetaInfResourceLocationComparator();
155
156        String includesKey = MetaInfConfigUtils.getIncludesKey(group, PREFIX) + "." + vendor;
157        String excludesKey = MetaInfConfigUtils.getExcludesKey(group, PREFIX) + "." + vendor;
158
159        Boolean relativePaths = env.getBoolean(RELATIVE_KEY, DEFAULT_GENERATE_RELATIVE_PATHS);
160
161        List<String> pathQualifiers = MetaInfUtils.getQualifiers(scanDir, project, Lists.newArrayList(qualifier), Lists.<String> newArrayList());
162
163        for (String pathQualifier : pathQualifiers) {
164            File outputFile = MetaInfUtils.getOutputFile(project, build, Optional.of(pathQualifier + PATH_SEPARATOR + vendor),
165                    Optional.of(location), Optional.of(type), group.name().toLowerCase());
166
167            Map<MetaInfGroup, String> defaultIncludes = getDefaultIncludes(pathQualifier, vendor, location, type);
168            Map<MetaInfGroup, String> defaultExcludes = getDefaultExcludes(defaultIncludes);
169            List<String> includes = SpringUtils.getNoneSensitiveListFromCSV(env, includesKey, defaultIncludes.get(group));
170            List<String> excludes = SpringUtils.getNoneSensitiveListFromCSV(env, excludesKey, defaultExcludes.get(group));
171
172            MetaInfContext context = MetaInfContext.builder(outputFile, encoding, scanDir).comparator(comparator)
173                    .includes(includes).excludes(excludes).relativePaths(relativePaths.booleanValue()).build();
174            metaInfContexts.add(context);
175        }
176
177        return metaInfContexts;
178    }
179
180    /**
181     * Generates the default mapping of included paths from the given {@code qualifier}, {@code vendor}, and
182     * {@code type}.
183     *
184     * @param qualifier the prefix to add to the initial resource path
185     * @param vendor the database vendor to include
186     * @param location the location of the data to include
187     * @param type the type of data to include
188     *
189     * @return the map of included paths
190     */
191    protected Map<MetaInfGroup, String> getDefaultIncludes(String qualifier, String vendor, MetaInfDataLocation location, MetaInfDataType type) {
192        Map<MetaInfGroup, String> defaultIncludes = Maps.newEnumMap(MetaInfGroup.class);
193
194        String resourcePath = ProjectUtils.getResourcePath(project.getGroupId(), project.getArtifactId());
195        List<String> paths = Lists.newArrayList(resourcePath, qualifier, vendor, location.name().toLowerCase(), type.name().toLowerCase());
196        String value = StringUtils.join(paths, PATH_SEPARATOR);
197
198        defaultIncludes.put(MetaInfGroup.SCHEMA, value + PATH_SEPARATOR + SCHEMA_SQL_PATH);
199        defaultIncludes.put(MetaInfGroup.DATA, value + PATH_SEPARATOR + ALL_SQL_PATH);
200        defaultIncludes.put(MetaInfGroup.CONSTRAINTS, value + PATH_SEPARATOR + CONSTRAINTS_SQL_PATH);
201        defaultIncludes.put(MetaInfGroup.OTHER, value + PATH_SEPARATOR + ALL_SQL_PATH);
202
203        return defaultIncludes;
204    }
205
206    /**
207     * Generates the default mapping of excluded paths from the {@code defaultIncludes} map.
208     *
209     * <p>
210     * Generally, nothing is excluded, but there is a special case with {@code MetaInfGroup.DATA} where it does not
211     * include either the {@code MetaInfGroup.SCHEMA} or {@code MetaInfGroup.CONSTRAINTS}.
212     * </p>
213     *
214     * @param defaultIncludes the map of included paths
215     *
216     * @return the map of excluded paths
217     */
218    protected Map<MetaInfGroup, String> getDefaultExcludes(Map<MetaInfGroup, String> defaultIncludes) {
219        Map<MetaInfGroup, String> defaultExcludes = Maps.newEnumMap(MetaInfGroup.class);
220
221        List<String> dataExcludes = Lists.newArrayList(defaultIncludes.get(MetaInfGroup.SCHEMA), defaultIncludes.get(MetaInfGroup.CONSTRAINTS));
222
223        defaultExcludes.put(MetaInfGroup.SCHEMA, NullUtils.NONE);
224        defaultExcludes.put(MetaInfGroup.DATA, StringUtils.join(dataExcludes, ","));
225        defaultExcludes.put(MetaInfGroup.CONSTRAINTS, NullUtils.NONE);
226        defaultExcludes.put(MetaInfGroup.OTHER, NullUtils.NONE);
227
228        return defaultExcludes;
229    }
230
231}