/* * * * This file is part of the Hesperides distribution. * * (https://github.com/voyages-sncf-technologies/hesperides) * * Copyright (c) 2016 VSCT. * * * * Hesperides is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as * * published by the Free Software Foundation, version 3. * * * * Hesperides is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * */ package com.vsct.dt.hesperides.files; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheException; import com.github.mustachejava.reflect.ReflectionObjectHandler; import com.github.mustachejava.util.Wrapper; import com.vsct.dt.hesperides.applications.*; import com.vsct.dt.hesperides.exception.runtime.MissingResourceException; import com.vsct.dt.hesperides.templating.models.HesperidesPropertiesModel; import com.vsct.dt.hesperides.templating.models.IterablePropertyModel; import com.vsct.dt.hesperides.templating.models.KeyValuePropertyModel; import com.vsct.dt.hesperides.templating.modules.Module; import com.vsct.dt.hesperides.templating.modules.ModuleKey; import com.vsct.dt.hesperides.templating.modules.ModulesAggregate; import com.vsct.dt.hesperides.templating.modules.Techno; import com.vsct.dt.hesperides.templating.modules.template.Template; import com.vsct.dt.hesperides.templating.modules.template.TemplateFileRights; import com.vsct.dt.hesperides.templating.modules.template.TemplateRights; import com.vsct.dt.hesperides.templating.packages.TemplatePackageKey; import com.vsct.dt.hesperides.templating.packages.TemplatePackagesAggregate; import com.vsct.dt.hesperides.templating.platform.*; import com.vsct.dt.hesperides.util.HesperidesVersion; import com.vsct.dt.hesperides.util.TemplateContentGenerator; import org.apache.commons.lang.StringUtils; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Created by william_montaz on 06/01/2015. */ public class Files { private final Applications applications; private final ModulesAggregate modules; private final TemplatePackagesAggregate templatePackages; private final DefaultMustacheFactory mustacheFactory; public Files(Applications applications, ModulesAggregate modules, TemplatePackagesAggregate templatePackages) { this.applications = applications; this.modules = modules; this.templatePackages = templatePackages; mustacheFactory = new NoHTMLEscapingMustacheFactory(); mustacheFactory.setObjectHandler(new PropertiesWithDotHandler()); } /** * List the files location for an instance of a specific platform * @param applicationName * @param platformName * @param path * @param moduleName * @param moduleVersion * @param instanceName * @return */ public Set<HesperidesFile> getLocations(String applicationName, String platformName, String path, String moduleName, String moduleVersion, boolean isModuleWorkingCopy, String instanceName, Boolean simulate) { PlatformKey platformKey = PlatformKey.withName(platformName) .withApplicationName(applicationName) .build(); PlatformData platform = applications.getPlatform(platformKey).orElseThrow(() -> new MissingResourceException("There is no platform " + applicationName + "/" + platformName)); PropertiesData properties = applications.getProperties(platformKey, generatePropertiesPath(path, moduleName, moduleVersion, isModuleWorkingCopy)); //Find the module and the related templates //Check that the module is defined in the platform and then get the real module representing it ModuleKey moduleKey = new ModuleKey( moduleName, new HesperidesVersion(moduleVersion, isModuleWorkingCopy) ); ApplicationModuleData applicationModule = platform.findModule(moduleName, moduleVersion, isModuleWorkingCopy, path).orElseThrow(() -> new MissingResourceException("There is no module "+moduleName+"/"+moduleVersion+"/"+(isModuleWorkingCopy ? "WorkingCopy":"Release" + " defined for platform "+applicationName+"/"+platformName +" at path "+path))); Module module = modules.getModule(moduleKey).orElseThrow(() -> new MissingResourceException("There is no module " + moduleName + "/" + moduleVersion + "/" + (isModuleWorkingCopy ? "WorkingCopy" : "Release"))); List<Template> templates = modules.getAllTemplates(moduleKey); //Dont forget the technos for(Techno techno : module.getTechnos()){ TemplatePackageKey packageInfo = new TemplatePackageKey( techno.getName(), new HesperidesVersion(techno.getVersion(), techno.isWorkingCopy()) ); Set<Template> technoTemplates = templatePackages.getAllTemplates(packageInfo); templates.addAll(technoTemplates); } //Get the instance InstanceData instance = applicationModule.getInstance(instanceName, simulate).orElseThrow(() -> new MissingResourceException("There is no instance " + instanceName + " in platform " + applicationName + "/" + platformName)); PropertiesData platformGlobalProperties = applications.getProperties(platformKey, "#"); Set<KeyValueValorisationData> hesperidesPlatformPredefinedScope = platform.generateHesperidesPredefinedScope(); hesperidesPlatformPredefinedScope.addAll(platformGlobalProperties.getKeyValueProperties()); Set<KeyValueValorisationData> hesperidesModulePredefinedScope = applicationModule.generateHesperidesPredefinedScope(); hesperidesPlatformPredefinedScope.addAll(hesperidesModulePredefinedScope); hesperidesPlatformPredefinedScope.addAll(instance.generatePredefinedScope()); //Generate the mustache scope correpsonding to the instance chosen and the properties path of the platform MustacheScope mustacheScope = properties.toMustacheScope(instance.getKeyValues(), hesperidesPlatformPredefinedScope); return templates.stream() .map(template -> getLocationFromMustacheScopeEvaluation(template, mustacheScope)) .collect(Collectors.toSet()); } private String generatePropertiesPath(String path, String moduleName, String moduleVersion, boolean isModuleWorkingCopy) { StringBuilder propertiesPath = new StringBuilder(); return propertiesPath.append(path).append("#").append(moduleName).append("#").append(moduleVersion).append("#").append(isModuleWorkingCopy ? "WORKINGCOPY" : "RELEASE").toString(); } /** * Helper method to evaluate the filename and location. * That implementation uses mustache to parse thoose simple fields, * we might consider to be more efficient by string replacement, * or guessing if we nedd replacement in the first place.... * * @param template * @param mustacheScope * @return */ private HesperidesFile getLocationFromMustacheScopeEvaluation(Template template, MustacheScope mustacheScope) { /* NOT EFFICIENT */ Mustache mustacheFilename = mustacheFactory.compile(new StringReader(template.getFilename()), "something"); String filename = TemplateContentGenerator.from(mustacheFilename).withScope(mustacheScope).generate(); /* NOT EFFICIENT */ Mustache mustacheLocation = mustacheFactory.compile(new StringReader(template.getLocation()), "something"); String location = TemplateContentGenerator.from(mustacheLocation).withScope(mustacheScope).generate(); return new HesperidesFile(template.getNamespace(), template.getName(), location,filename, convertRights(template.getRights())); } /** * Convert TemplateRights to HesperidesFileRights * @param rights * @return */ private static HesperidesFileRights convertRights(TemplateRights rights) { HesperidesFileRights hfr = null; if (rights != null) { HesperidesRight user = null; HesperidesRight group = null; HesperidesRight other = null; if (rights.getUser() != null) { user = convertRight(rights.getUser()); } if (rights.getGroup() != null) { group = convertRight(rights.getGroup()); } if (rights.getOther() != null) { other = convertRight(rights.getOther()); } hfr = new HesperidesFileRights(user, group, other); } return hfr; } /** * Convert TemplateFileRights to HesperidesRight * @param rights TemplateFileRights * @return HesperidesRight */ private static HesperidesRight convertRight(final TemplateFileRights rights) { HesperidesRight group; group = new HesperidesRight(rights.isRead(), rights.isWrite(), rights.isExecute()); return group; } /** * Evaluate a template with properties * @param applicationName * @param platformName * @param path * @param moduleName * @param moduleVersion * @param isModuleWorkingCopy * @param instanceName * @return */ public String getFile(String applicationName, String platformName, String path, String moduleName, String moduleVersion, boolean isModuleWorkingCopy, String instanceName, String templateNamespace, String templateName, HesperidesPropertiesModel model, Boolean simulate) { PlatformKey platformKey = PlatformKey.withName(platformName) .withApplicationName(applicationName) .build(); PlatformData platform = applications.getPlatform(platformKey).orElseThrow(() -> new MissingResourceException("There is no platform " + applicationName + "/" + platformName)); PropertiesData properties = applications.getSecuredProperties(platformKey, generatePropertiesPath(path, moduleName, moduleVersion, isModuleWorkingCopy), model); PropertiesData platformGlobalProperties = applications.getProperties(platformKey, "#"); Set<KeyValueValorisationData> hesperidesPlatformPredefinedScope = platform.generateHesperidesPredefinedScope(); hesperidesPlatformPredefinedScope.addAll(platformGlobalProperties.getKeyValueProperties()); ApplicationModuleData applicationModule = platform.findModule(moduleName, moduleVersion, isModuleWorkingCopy, path).orElseThrow(() -> new MissingResourceException("There is no module "+moduleName+"/"+moduleVersion+"/"+(isModuleWorkingCopy ? "WorkingCopy":"Release" + " defined for platform "+applicationName+"/"+platformName +" at path "+path))); //Get the instance InstanceData instance = applicationModule.getInstance(instanceName, simulate).orElseThrow(() -> new MissingResourceException("There is no instance " + instanceName + " in platform " + applicationName + "/" + platformName)); Set<KeyValueValorisationData> hesperidesModulePredefinedScope = applicationModule.generateHesperidesPredefinedScope(); hesperidesPlatformPredefinedScope.addAll(hesperidesModulePredefinedScope); hesperidesPlatformPredefinedScope.addAll(instance.generatePredefinedScope()); //Generate the mustache scope correpsonding to the instance chosen and the properties path of the platform MustacheScope mustacheScope = properties.toMustacheScope(instance.getKeyValues(), hesperidesPlatformPredefinedScope, true); //Find the template ModuleKey moduleKey = new ModuleKey( moduleName, new HesperidesVersion(moduleVersion, isModuleWorkingCopy) ); Template template = manageModule(moduleName, moduleVersion, isModuleWorkingCopy, templateNamespace, templateName, mustacheScope, moduleKey); //Test template is null if(template == null){ throw new MissingResourceException("Could not find template "+templateNamespace+"/"+templateName); } Mustache mustacheTemplate = mustacheFactory.compile(new StringReader(template.getContent()), "something"); return TemplateContentGenerator.from(mustacheTemplate).withScope(mustacheScope).generate(); } /** * Manages an iterable property. * This makes use of recursion to manage inner iterable properties if existing * * @param scopes : the mustache scope collection * @param itpm : the iterable property model * @param templateNamespace : the template workspace * @param templateName : the template name */ private void manageIterableProperty (ArrayList<MustacheScope> scopes, IterablePropertyModel itpm, String templateNamespace, String templateName){ scopes.forEach(scope -> { itpm.getFields().forEach(p -> { if ( p instanceof IterablePropertyModel ){ // this is an inner iterable manageIterableProperty((ArrayList<MustacheScope>) scope.get(p.getName()), (IterablePropertyModel) p, templateNamespace, templateName); }else{ // check if the property exists in scope. boolean _exists = isInScope(p.getName(), scope); // required but not existing if (!_exists && p.isRequired()){ throw new MissingResourceException(String.format("Property '%s' in template '%s/%s' must be set.", p.getName(), templateNamespace, templateName)); } // has default value and not existing if (!_exists && StringUtils.isNotEmpty(p.getDefaultValue())){ scope.put(p.getName(), p.getDefaultValue()); } // has pattern if (StringUtils.isNotEmpty(p.getPattern())){ // get the value String value = findProperty(p.getName(), scope); Pattern pattern = Pattern.compile(p.getPattern()); Matcher matcher = pattern.matcher(value); if (!matcher.matches()){ throw new MissingResourceException(String.format( "Property '%s' in template '%s/%s' not match regular expression '%s'.", p.getName(), templateNamespace, templateName, p.getPattern())); } } } }); }); } /** * Manage module template. * * @param moduleName * @param moduleVersion * @param isModuleWorkingCopy * @param templateNamespace * @param templateName * @param mustacheScope * @param moduleKey * * @return */ private Template manageModule(final String moduleName, final String moduleVersion, final boolean isModuleWorkingCopy, final String templateNamespace, final String templateName, final MustacheScope mustacheScope, final ModuleKey moduleKey) { Template template = null; if(templateNamespace.startsWith("modules")){ template = modules.getTemplate(moduleKey, templateName).orElseThrow(() -> new MissingResourceException("Could not find template " + templateName + " in module " + moduleName + "/" + moduleVersion + "/" + (isModuleWorkingCopy ? "WorkingCopy" : "Release"))); } else if(templateNamespace.startsWith("packages")) { template = templatePackages.getTemplate(templateNamespace, templateName).orElseThrow(() -> new MissingResourceException("Could not find template "+templateNamespace+"/"+templateName)); } HesperidesPropertiesModel templateModel = modules.getModel(moduleKey).orElseThrow(() -> new MissingResourceException("Could not find module " + moduleKey)); boolean exists; // Taking care of key-value properties for (KeyValuePropertyModel kvpm : templateModel.getKeyValueProperties()) { exists = isInScope(kvpm.getName(), mustacheScope); if (kvpm.isRequired() && !exists) { throw new MissingResourceException(String.format("Property '%s' in template '%s/%s' must be set.", kvpm.getName(), templateNamespace, templateName)); } if (StringUtils.isNotEmpty(kvpm.getDefaultValue()) && !exists) { mustacheScope.put(kvpm.getName(), kvpm.getDefaultValue()); } if (StringUtils.isNotEmpty(kvpm.getPattern())) { String propVal = findProperty(kvpm.getName(), mustacheScope); if (propVal != null) { Pattern p = Pattern.compile(kvpm.getPattern()); Matcher m = p.matcher(propVal); if (!m.matches()) { throw new MissingResourceException(String.format( "Property '%s' in template '%s/%s' not match regular expression '%s'.", kvpm.getName(), templateNamespace, templateName, kvpm.getPattern())); } } } } // Taking care of iterable properties for (IterablePropertyModel itpm : templateModel.getIterableProperties()){ // Get the valuation for this if (mustacheScope.keySet().contains(itpm.getName())) { // The the scopes ArrayList<MustacheScope> scopes = (ArrayList<MustacheScope>) mustacheScope.get(itpm.getName()); // Manage it as iterable property manageIterableProperty(scopes, itpm, templateNamespace, templateName); } } return template; } /** * Check if found property in scope. * * @param name property name. * @param mustacheScope scope * * @return true if found else otherwise. */ private static boolean isInScope(final String name, final MustacheScope mustacheScope) { boolean found = false; for (String kvv : mustacheScope.keySet()) { if (name.equals(kvv)) { found = true; break; } } return found; } /** * Find property in scope. * * @param name property name. * @param mustacheScope scope * * @return true if not found. */ private static String findProperty(final String name, final MustacheScope mustacheScope) { String result = null; for (Map.Entry<String, Object> kvv : mustacheScope.entrySet()) { if (name.equals(kvv.getKey())) { result = kvv.getValue().toString(); break; } } return result; } /* This handlers helps using properties with dotted name like com.vsct.hesperides */ /* it actually looks for the entire dotted property instead of trying to go inside nested objects */ private static class PropertiesWithDotHandler extends ReflectionObjectHandler { @Override public Wrapper find(final String name, final List<Object> scopes) { Object scope; String real_name = name.split("[|]")[0].trim(); int scope_index = scopes.size() - 1; final ListIterator<Object> scopeIterator = scopes.listIterator(scopes.size()); // We must search from local scope to global scope in case of iterable properties. while (scopeIterator.hasPrevious()) { scope = scopeIterator.previous(); if (scope instanceof Map && ((Map) scope).containsKey(real_name)) { final int index = scope_index; return scopes1 -> ((Map) scopes1.get(index)).get(real_name); } scope_index--; } return super.find(name, scopes); } } private static class NoHTMLEscapingMustacheFactory extends DefaultMustacheFactory { @Override public void encode(String value, Writer writer) { try { writer.write(value); } catch (IOException e) { //Should never be here e.printStackTrace(); throw new MustacheException("Impossible to execute encode method properly"); } } } }