/*
* (C) Copyright 2015 Netcentric AG.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package biz.netcentric.cq.tools.actool.configreader;
import static biz.netcentric.cq.tools.actool.history.AcInstallationLog.msHumanReadable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.lang.time.StopWatch;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import biz.netcentric.cq.tools.actool.configmodel.AcConfiguration;
import biz.netcentric.cq.tools.actool.configmodel.AceBean;
import biz.netcentric.cq.tools.actool.configmodel.AcesConfig;
import biz.netcentric.cq.tools.actool.configmodel.AuthorizableConfigBean;
import biz.netcentric.cq.tools.actool.configmodel.AuthorizablesConfig;
import biz.netcentric.cq.tools.actool.configmodel.GlobalConfiguration;
import biz.netcentric.cq.tools.actool.helper.Constants;
import biz.netcentric.cq.tools.actool.history.AcInstallationLog;
import biz.netcentric.cq.tools.actool.validators.AceBeanValidator;
import biz.netcentric.cq.tools.actool.validators.AuthorizableValidator;
import biz.netcentric.cq.tools.actool.validators.ConfigurationsValidator;
import biz.netcentric.cq.tools.actool.validators.GlobalConfigurationValidator;
import biz.netcentric.cq.tools.actool.validators.ObsoleteAuthorizablesValidator;
import biz.netcentric.cq.tools.actool.validators.YamlConfigurationsValidator;
import biz.netcentric.cq.tools.actool.validators.exceptions.AcConfigBeanValidationException;
import biz.netcentric.cq.tools.actool.validators.impl.AceBeanValidatorImpl;
import biz.netcentric.cq.tools.actool.validators.impl.AuthorizableValidatorImpl;
@Service
@Component
public class YamlConfigurationMerger implements ConfigurationMerger {
private static final Logger LOG = LoggerFactory.getLogger(YamlConfigurationMerger.class);
@Reference
YamlMacroProcessor yamlMacroProcessor;
@Reference
ObsoleteAuthorizablesValidator obsoleteAuthorizablesValidator;
@Override
public AcConfiguration getMergedConfigurations(
final Map<String, String> configFileContentByFilename,
final AcInstallationLog history,
final ConfigReader configReader, Session session) throws RepositoryException,
AcConfigBeanValidationException {
StopWatch sw = new StopWatch();
sw.start();
final GlobalConfiguration globalConfiguration = new GlobalConfiguration();
final AuthorizablesConfig mergedAuthorizablesBeansfromConfig = new AuthorizablesConfig();
final AcesConfig mergedAceBeansFromConfig = new AcesConfig();
final Set<String> authorizableIdsFromAllConfigs = new HashSet<String>(); // needed for detection of doubled defined groups in
// configurations
final Set<String> obsoleteAuthorizables = new HashSet<String>();
final Yaml yamlParser = new Yaml();
final ConfigurationsValidator configurationsValidator = new YamlConfigurationsValidator();
for (final Map.Entry<String, String> entry : configFileContentByFilename.entrySet()) {
String sourceFile = entry.getKey();
history.addMessage(LOG, "Found configuration file " + sourceFile);
List<LinkedHashMap> yamlRootList = (List<LinkedHashMap>) yamlParser.load(entry.getValue());
yamlRootList = yamlMacroProcessor.processMacros(yamlRootList, history, session);
// set merged config per file to ensure it is there in case of validation errors (for success, the actual merged config is set
// after this loop)
history.setMergedAndProcessedConfig("# File " + sourceFile + "\n" + yamlParser.dump(yamlRootList));
final Set<String> sectionIdentifiers = new LinkedHashSet<String>();
// put all section identifiers of current configuration into a set
for (int i = 0; i < yamlRootList.size(); i++) {
sectionIdentifiers.addAll(yamlRootList.get(i).keySet());
}
configurationsValidator.validateSectionIdentifiers(sectionIdentifiers, sourceFile);
// --- global configuration section
try {
globalConfiguration.merge(configReader.getGlobalConfiguration(yamlRootList));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid global configuration in " + sourceFile + ": " + e, e);
}
// --- authorizables config section
final AuthorizableValidator authorizableValidator = new AuthorizableValidatorImpl(Constants.GROUPS_ROOT, Constants.USERS_ROOT);
final AuthorizablesConfig groupsFromThisConfig = configReader.getGroupConfigurationBeans(
yamlRootList, authorizableValidator);
// add AuthorizableConfigBeans built from current configuration to set containing AuthorizableConfigBeans from all
// configurations
if (groupsFromThisConfig != null) {
mergedAuthorizablesBeansfromConfig.addAll(groupsFromThisConfig);
}
final AuthorizablesConfig usersMapFromThisConfig = configReader.getUserConfigurationBeans(
yamlRootList, authorizableValidator);
if (usersMapFromThisConfig != null) {
mergedAuthorizablesBeansfromConfig.addAll(usersMapFromThisConfig);
}
// validate duplicate authorizables
final Set<String> authorizableIdsFromCurrentConfig = new HashSet<String>();
if (groupsFromThisConfig != null) {
authorizableIdsFromCurrentConfig.addAll(groupsFromThisConfig.getAuthorizableIds());
}
if (usersMapFromThisConfig != null) {
authorizableIdsFromCurrentConfig.addAll(usersMapFromThisConfig.getAuthorizableIds());
}
if (authorizableIdsFromCurrentConfig != null) {
configurationsValidator.validateDuplicateAuthorizables(authorizableIdsFromAllConfigs, authorizableIdsFromCurrentConfig,
sourceFile);
// add IDs from authorizables from current configuration to set
authorizableIdsFromAllConfigs.addAll(authorizableIdsFromCurrentConfig);
}
// --- authorizables config section
final AceBeanValidator aceBeanValidator = new AceBeanValidatorImpl(authorizableIdsFromAllConfigs);
final Set<AceBean> currentAceBeansFromConfig = configReader.getAceConfigurationBeans(yamlRootList, aceBeanValidator, session);
configurationsValidator.validateKeepOrder(mergedAceBeansFromConfig, currentAceBeansFromConfig, sourceFile);
// add AceBeans built from current configuration to set containing AceBeans from all configurations
if (currentAceBeansFromConfig != null) {
mergedAceBeansFromConfig.addAll(currentAceBeansFromConfig);
}
configurationsValidator.validateInitialContentForNoDuplicates(mergedAceBeansFromConfig);
// --- obsolete authorizables config section
obsoleteAuthorizables.addAll(configReader.getObsoluteAuthorizables(yamlRootList));
obsoleteAuthorizablesValidator.validate(obsoleteAuthorizables, authorizableIdsFromAllConfigs, sourceFile);
}
ensureIsMemberOfIsUsedWherePossible(mergedAuthorizablesBeansfromConfig, history);
GlobalConfigurationValidator.validate(globalConfiguration);
AcConfiguration acConfiguration = new AcConfiguration();
acConfiguration.setGlobalConfiguration(globalConfiguration);
acConfiguration.setAuthorizablesConfig(mergedAuthorizablesBeansfromConfig);
acConfiguration.setAceConfig(mergedAceBeansFromConfig);
acConfiguration.setObsoleteAuthorizables(obsoleteAuthorizables);
history.setMergedAndProcessedConfig(
"# Merged configuration of " + configFileContentByFilename.size() + " files \n" + yamlParser.dump(acConfiguration));
history.addMessage(LOG, "Loaded configuration in " + msHumanReadable(sw.getTime()));
return acConfiguration;
}
void ensureIsMemberOfIsUsedWherePossible(AuthorizablesConfig mergedAuthorizablesBeansfromConfig,
AcInstallationLog history) {
for (AuthorizableConfigBean group : mergedAuthorizablesBeansfromConfig) {
if (!group.isGroup()) {
continue;
}
final String groupName = group.getAuthorizableId();
String[] origMembersArr = group.getMembers();
if ((origMembersArr == null) || (origMembersArr.length == 0)) {
continue;
}
final List<String> members = new ArrayList<String>(Arrays.asList(origMembersArr));
Iterator<String> membersIt = members.iterator();
while (membersIt.hasNext()) {
String member = membersIt.next();
AuthorizableConfigBean groupForIsMemberOf = mergedAuthorizablesBeansfromConfig.getAuthorizableConfig(member);
boolean memberContainedInConfig = groupForIsMemberOf != null;
if (memberContainedInConfig) {
groupForIsMemberOf.addIsMemberOf(groupName);
membersIt.remove();
history.addWarning(LOG, "Group " + group.getAuthorizableId() + " is declaring member " + member
+ " - moving relationship to isMemberOf of authorizable " + groupForIsMemberOf.getAuthorizableId()
+ " (always prefer using isMemberOf over members if possible referenced member is availalbe in configuration)");
}
}
group.setMembers(members.toArray(new String[members.size()]));
}
}
}