/*
* Tanaguru - Automated webpage assessment
* Copyright (C) 2008-2015 Tanaguru.org
*
* This file is part of Tanaguru.
*
* Tanaguru is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact us by mail: tanaguru AT tanaguru DOT org
*/
package org.tanaguru.webapp.controller;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.tanaguru.entity.audit.Audit;
import org.tanaguru.entity.audit.AuditStatus;
import org.tanaguru.entity.parameterization.Parameter;
import org.tanaguru.entity.parameterization.ParameterElement;
import org.tanaguru.entity.service.parameterization.ParameterElementDataService;
import org.tanaguru.entity.subject.Page;
import org.tanaguru.entity.subject.Site;
import org.tanaguru.webapp.command.AuditSetUpCommand;
import org.tanaguru.webapp.entity.contract.Contract;
import org.tanaguru.webapp.entity.contract.ScopeEnum;
import org.tanaguru.webapp.entity.option.OptionElement;
import org.tanaguru.webapp.entity.service.option.OptionElementDataService;
import org.tanaguru.webapp.entity.user.User;
import org.tanaguru.webapp.exception.KrashAuditException;
import org.tanaguru.webapp.exception.LostInSpaceException;
import org.tanaguru.webapp.orchestrator.TanaguruOrchestrator;
import org.tanaguru.webapp.util.HttpStatusCodeFamily;
import org.tanaguru.webapp.util.TgolKeyStore;
import org.tanaguru.webapp.util.webapp.ExposablePropertyPlaceholderConfigurer;
import org.tanaguru.webapp.voter.restriction.RestrictionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
/**
*
* @author jkowalczyk
*/
@Controller
public class AuditLauncherController extends AbstractAuditDataHandlerController {
private static final Logger LOGGER = Logger.getLogger(AuditLauncherController.class);
private String groupePagesName = "";
/**
* The TanaguruOrchestrator instance needed to launch the audit process
*/
private TanaguruOrchestrator tanaguruExecutor;
@Autowired
public final void setTanaguruExecutor(TanaguruOrchestrator tanaguruExecutor) {
this.tanaguruExecutor = tanaguruExecutor;
}
/**
* The RestrictionHandler instance needed to decide
*/
private RestrictionHandler restrictionHandler;
@Autowired
public final void setRestrictionHandler(RestrictionHandler restrictionHandler) {
this.restrictionHandler = restrictionHandler;
}
/**
* The ParameterElementDataService instance
*/
private ParameterElementDataService parameterElementDataService;
@Autowired
public void setParameterElementDataService(ParameterElementDataService parameterElementDataService) {
this.parameterElementDataService = parameterElementDataService;
setParameterElementMap(parameterElementDataService);
}
private final Map<String, ParameterElement> parameterElementMap = new HashMap();
private void setParameterElementMap(ParameterElementDataService peds) {
for (ParameterElement pe : peds.findAll()) {
parameterElementMap.put(pe.getParameterElementCode(), pe);
}
}
/**
* default audit page parameter set.
*/
Set<Parameter> auditPageParamSet = null;
private ExposablePropertyPlaceholderConfigurer exposablePropertyPlaceholderConfigurer;
@Autowired
public final void setExposablePropertyPlaceholderConfigurer(ExposablePropertyPlaceholderConfigurer exposablePropertyPlaceholderConfigurer) {
this.exposablePropertyPlaceholderConfigurer = exposablePropertyPlaceholderConfigurer;
}
/**
*
*/
private List<String> emailSentToUserExclusionList;
/**
* Direct call to the property place holder configurer due to exposition
* context
*
* @return
*/
public List<String> getEmailSentToUserExclusionList() {
if (emailSentToUserExclusionList == null) {
String rawList = exposablePropertyPlaceholderConfigurer.
getResolvedProps().get(TgolKeyStore.EMAIL_SENT_TO_USER_EXCLUSION_CONF_KEY);
emailSentToUserExclusionList = Arrays.asList(rawList.split(";"));
}
return emailSentToUserExclusionList;
}
/**
* The user options that have to be converted as audit parameters
*/
private List<String> userOption;
public List<String> getUserOption() {
return userOption;
}
public void setUserOption(List<String> userOption) {
this.userOption = userOption;
}
/**
* The user options that have to be converted as audit parameters and that
* depend on the selected referential
*/
private List<String> userOptionDependingOnReferential;
public List<String> getUserOptionDependingOnReferential() {
return userOptionDependingOnReferential;
}
public void setUserOptionDependingOnReferential(List<String> userOptionDependingOnReferential) {
this.userOptionDependingOnReferential = userOptionDependingOnReferential;
}
private OptionElementDataService optionElementDataService;
public OptionElementDataService getOptionElementDataService() {
return optionElementDataService;
}
@Autowired
public void setOptionElementDataService(OptionElementDataService optionElementDataService) {
this.optionElementDataService = optionElementDataService;
}
public AuditLauncherController() {
super();
}
/**
* This methods enables an authenticated user to launch an audit.
*
* @param auditSetUpCommand
* @param locale
* @param model
* @return
*/
public String launchAudit(
final AuditSetUpCommand auditSetUpCommand,
final Locale locale,
Model model) {
Contract contract = getContractDataService().read(auditSetUpCommand.getContractId());
if (isContractExpired(contract)) {
return displayContractView(contract, model);
} else {
// before launching the audit, we check whether any restriction on the
//contract forbids it.
ScopeEnum scope = auditSetUpCommand.getScope();
String checkResult = restrictionHandler.checkRestriction(contract, getClientIpAddress(), scope);
if (!checkResult.equalsIgnoreCase(TgolKeyStore.ACT_ALLOWED)) {
return checkResult;
}
if (scope.equals(ScopeEnum.PAGE)
|| scope.equals(ScopeEnum.FILE)) {
return preparePageAudit(auditSetUpCommand, contract, locale, scope, model);
}
String url = getContractDataService().getUrlFromContractOption(contract);
if (scope.equals(ScopeEnum.DOMAIN)) {
tanaguruExecutor.auditSite(
contract,
url,
getClientIpAddress(),
getUserParamSet(auditSetUpCommand, contract.getId(), -1, url),
locale);
model.addAttribute(TgolKeyStore.TESTED_URL_KEY, url);
} else if (scope.equals(ScopeEnum.SCENARIO)) {
tanaguruExecutor.auditScenario(
contract,
auditSetUpCommand.getScenarioId(),
getClientIpAddress(),
getUserParamSet(auditSetUpCommand, contract.getId(), -1, url),
locale);
model.addAttribute(TgolKeyStore.SCENARIO_NAME_KEY, auditSetUpCommand.getScenarioName());
model.addAttribute(TgolKeyStore.SCENARIO_ID_KEY, auditSetUpCommand.getScenarioId());
}
model.addAttribute(TgolKeyStore.CONTRACT_ID_KEY, contract.getId());
model.addAttribute(TgolKeyStore.CONTRACT_NAME_KEY, contract.getLabel());
return TgolKeyStore.AUDIT_IN_PROGRESS_VIEW_NAME;
}
}
/**
* This methods controls the validity of the form and launch an audit with
* values populated by the user. In case of audit failure, an appropriate
* message is displayed
*
* @param pageAuditSetUpCommand
* @param contract
* @param locale
* @param auditScope
* @param model
* @return
*/
private String preparePageAudit(
final AuditSetUpCommand auditSetUpCommand,
final Contract contract,
final Locale locale,
final ScopeEnum auditScope,
Model model) {
Audit audit;
boolean isPageAudit = true;
// if the form is correct, we launch the audit
try {
if (auditScope.equals(ScopeEnum.FILE)) {
audit = launchUploadAudit(contract, auditSetUpCommand, locale);
isPageAudit = false;
} else {
audit = launchPageAudit(contract, auditSetUpCommand, locale);
}
} catch (KrashAuditException kae) {
return TgolKeyStore.OUPS_VIEW_NAME;
}
// if the audit lasted more than expected, we return a "audit in progress"
// page and send an email when it's ready
if (audit == null) {
model.addAttribute(TgolKeyStore.TESTED_URL_KEY, auditSetUpCommand.getUrlList().get(0));
model.addAttribute(TgolKeyStore.CONTRACT_ID_KEY, contract.getId());
model.addAttribute(TgolKeyStore.CONTRACT_NAME_KEY, contract.getLabel());
model.addAttribute(TgolKeyStore.IS_PAGE_AUDIT_KEY, isPageAudit);
if (!getEmailSentToUserExclusionList().contains(contract.getUser().getEmail1())) {
model.addAttribute(TgolKeyStore.IS_USER_NOTIFIED_KEY, true);
}
return TgolKeyStore.GREEDY_AUDIT_VIEW_NAME;
}
if (audit.getStatus() != AuditStatus.COMPLETED) {
return prepareFailedAuditData(audit, model);
}
if (audit.getSubject() instanceof Site) {
// in case of group of page, we display the list of audited pages
model.addAttribute(TgolKeyStore.AUDIT_ID_KEY, audit.getId());
model.addAttribute(TgolKeyStore.STATUS_KEY, HttpStatusCodeFamily.f2xx);
return TgolKeyStore.PAGE_LIST_XXX_VIEW_REDIRECT_NAME;
} else if (audit.getSubject() instanceof Page) {
model.addAttribute(TgolKeyStore.WEBRESOURCE_ID_KEY, audit.getSubject().getId());
return TgolKeyStore.RESULT_PAGE_VIEW_REDIRECT_NAME;
}
throw new LostInSpaceException(getCurrentUser());
}
/**
* This method launches the audit process using the tanaguru orchestrator
* bean
*
* @param contract
* @param auditSetUpCommand
* @param locale
* @return
*/
private Audit launchPageAudit(
final Contract contract,
final AuditSetUpCommand auditSetUpCommand,
final Locale locale) {
int urlCounter = 0;
// 10 String are received from the form even if these String are empty.
// We sort the string and only keep the not empty ones.
List<String> trueUrl = new ArrayList();
for (String str : auditSetUpCommand.getUrlList()) {
if (StringUtils.isNotBlank(str)) {
trueUrl.add(urlCounter, str.trim());
urlCounter++;
}
}
Set<Parameter> paramSet = getUserParamSet(auditSetUpCommand, contract.getId(), trueUrl.size(), trueUrl.get(0));
if (trueUrl.size() == 1) {
LOGGER.debug("Launch " + trueUrl.get(0) + " audit in page mode");
return tanaguruExecutor.auditPage(
contract,
trueUrl.get(0),
getClientIpAddress(),
paramSet,
locale);
} else if (trueUrl.size() > 1) {
String[] finalUrlTab = new String[trueUrl.size()];
for (int i = 0; i < trueUrl.size(); i++) {
finalUrlTab[i] = trueUrl.get(i);
}
groupePagesName = extractGroupNameFromUrl(finalUrlTab[0]);
LOGGER.debug("Launch " + groupePagesName + " audit in group of pages mode");
return tanaguruExecutor.auditSite(
contract,
groupePagesName,
trueUrl,
getClientIpAddress(),
paramSet,
locale);
} else {
return null;
}
}
/**
*
* @param contract
* @param auditSetUpCommand
* @param locale
* @return
*/
private Audit launchUploadAudit(
final Contract contract,
final AuditSetUpCommand auditSetUpCommand,
final Locale locale) {
Map<String, String> fileMap = auditSetUpCommand.getFileMap();
return tanaguruExecutor.auditPageUpload(
contract,
fileMap,
getClientIpAddress(),
getUserParamSet(auditSetUpCommand, contract.getId(), fileMap.size(), null),
locale);
}
/**
* This methods extracts the name of a group of pages from an url
*
* @param url
* @return
*/
private String extractGroupNameFromUrl(String url) {
int fromIndex;
if (url.startsWith(TgolKeyStore.HTTP_PREFIX)) {
fromIndex = TgolKeyStore.HTTP_PREFIX.length();
} else if (url.startsWith(TgolKeyStore.HTTPS_PREFIX)) {
fromIndex = TgolKeyStore.HTTPS_PREFIX.length();
} else {
url = TgolKeyStore.HTTP_PREFIX + url;
fromIndex = TgolKeyStore.HTTP_PREFIX.length();
}
if (url.indexOf(TgolKeyStore.SLASH_CHAR, fromIndex) != -1) {
return url.substring(0, url.indexOf(TgolKeyStore.SLASH_CHAR, fromIndex));
} else {
return url;
}
}
/**
* This method gets the default parameters for an audit and eventually
* override some of them in case of contract restriction.
*
* @param auditSetUpCommand
* @param contractId
* @param nbOfPages
* @param url
* @return
*/
private Set<Parameter> getUserParamSet(AuditSetUpCommand auditSetUpCommand, Long contractId, int nbOfPages, String url) {
Set<Parameter> paramSet;
Set<Parameter> userParamSet = new HashSet();
if (auditSetUpCommand != null) {
// The default parameter set corresponds to a site audit
// If the launched audit is of any other type, we retrieve another
// parameter set
if (auditSetUpCommand.getScope().equals(ScopeEnum.SCENARIO)) {
paramSet = getAuditScenarioParameterSet();
} else if (!auditSetUpCommand.getScope().equals(ScopeEnum.DOMAIN)) {
paramSet = getAuditPageParameterSet(nbOfPages);
} else {
paramSet = getDefaultParamSet();
}
for (Map.Entry<String, String> entry : auditSetUpCommand.getAuditParameter().entrySet()) {
Parameter param = getParameterDataService().getParameter(parameterElementMap.get(entry.getKey()), entry.getValue());
userParamSet.add(param);
}
paramSet = getParameterDataService().updateParameterSet(paramSet, userParamSet);
paramSet = setLevelParameter(paramSet, auditSetUpCommand.getLevel());
} else {
paramSet = getDefaultParamSet();
Collection<OptionElement> optionElementSet =
getContractDataService().read(contractId).getOptionElementSet();
for (Parameter param : paramSet) {
for (OptionElement optionElement : optionElementSet) {
if (optionElement.getOption().getCode().
equalsIgnoreCase(param.getParameterElement().getParameterElementCode())) {
param = getParameterDataService().getParameter(param.getParameterElement(), optionElement.getValue());
break;
}
}
userParamSet.add(param);
}
paramSet = getParameterDataService().updateParameterSet(paramSet, userParamSet);
}
return (auditSetUpCommand.getLevel() != null )? setUserParameters(paramSet, auditSetUpCommand.getLevel().split(";")[0]) : paramSet;
}
/**
*
* @param paramSet
* @param url
* @return
*/
private Set<Parameter> setLevelParameter(Set<Parameter> paramSet, String level) {
Parameter levelParameter = getParameterDataService().getLevelParameter(level);
paramSet = getParameterDataService().updateParameter(paramSet, levelParameter);
return paramSet;
}
/**
* The default parameter set embeds a depth value that corresponds to the
* site audit. We need here to replace this parameter by a parameter value
* equals to 0.
*
* @return
*/
private Set<Parameter> getAuditPageParameterSet(int nbOfPages) {
if (auditPageParamSet == null) {
Set<Parameter> paramSet = new HashSet();
ParameterElement depthParameterElement =
parameterElementDataService
.getParameterElement(TgolKeyStore.DEPTH_PARAM_KEY);
ParameterElement maxDocParameterElement =
parameterElementDataService
.getParameterElement(TgolKeyStore.MAX_DOCUMENT_PARAM_KEY);
Parameter depthParameter =
getParameterDataService()
.getParameter(depthParameterElement, TgolKeyStore.DEPTH_PAGE_PARAM_VALUE);
Parameter maxDocParameter =
getParameterDataService()
.getParameter(maxDocParameterElement, String.valueOf(nbOfPages));
paramSet.add(depthParameter);
paramSet.add(maxDocParameter);
auditPageParamSet = getParameterDataService().updateParameterSet(getDefaultParamSet(), paramSet);
}
return auditPageParamSet;
}
/**
* The default parameter set embeds a depth value that corresponds to the
* site audit. We need here to replace this parameter by a parameter value
* equals to 0.
*
* @return
*/
private Set<Parameter> getAuditScenarioParameterSet() {
Set<Parameter> scenarioParamSet = getDefaultParamSet();
Set<Parameter> parameterToRemove = new HashSet();
for (Parameter param : scenarioParamSet) {
if (StringUtils.equals(param.getParameterElement().getParameterElementCode(), TgolKeyStore.DEPTH_PARAM_KEY)
|| StringUtils.equals(param.getParameterElement().getParameterElementCode(), TgolKeyStore.MAX_DOCUMENT_PARAM_KEY)
|| StringUtils.equals(param.getParameterElement().getParameterElementCode(), TgolKeyStore.MAX_DURATION_PARAM_KEY)
|| StringUtils.equals(param.getParameterElement().getParameterElementCode(), TgolKeyStore.EXCLUSION_URL_LIST_PARAM_KEY)) {
parameterToRemove.add(param);
}
}
scenarioParamSet.removeAll(parameterToRemove);
return scenarioParamSet;
}
/**
* Some user options have to be converted as parameters and added to the
* general audit parameters.
*
* @param paramSet
* @param referentialKey
* @return
*/
private Set<Parameter> setUserParameters(Set<Parameter> paramSet, String referentialKey) {
User user = getCurrentUser();
Collection<OptionElement> optionElementSet = new HashSet();
for (String optionFamily : userOptionDependingOnReferential) {
optionElementSet.addAll(optionElementDataService.getOptionElementFromUserAndFamilyCode(user, referentialKey + "_" + optionFamily));
}
for (String optionFamily : userOption) {
optionElementSet.addAll(optionElementDataService.getOptionElementFromUserAndFamilyCode(user, optionFamily));
}
paramSet.addAll(getParameterDataService().getParameterSetFromOptionElementSet(optionElementSet));
return paramSet;
}
}