/*- * ========================LICENSE_START================================= * AEM Permission Management * %% * Copyright (C) 2013 Cognifide Limited * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * =========================LICENSE_END================================== */ package com.cognifide.apm.core.scripts; import com.cognifide.apm.api.actions.ActionResult; import com.cognifide.apm.api.actions.Context; import com.cognifide.apm.api.actions.SessionSavingMode; import com.cognifide.apm.api.actions.SessionSavingPolicy; import com.cognifide.apm.api.exceptions.ActionCreationException; import com.cognifide.apm.api.exceptions.ExecutionException; import com.cognifide.apm.api.scripts.Script; import com.cognifide.apm.api.services.DefinitionsProvider; import com.cognifide.apm.api.services.ExecutionMode; import com.cognifide.apm.api.services.ScriptFinder; import com.cognifide.apm.api.services.ScriptManager; import com.cognifide.apm.api.status.Status; import com.cognifide.apm.core.Property; import com.cognifide.apm.core.actions.ActionDescriptor; import com.cognifide.apm.core.actions.ActionFactory; import com.cognifide.apm.core.actions.executor.ActionExecutor; import com.cognifide.apm.core.actions.executor.ActionExecutorFactory; import com.cognifide.apm.core.executors.ContextImpl; import com.cognifide.apm.core.grammar.ScriptRunner; import com.cognifide.apm.core.history.History; import com.cognifide.apm.core.history.HistoryEntry; import com.cognifide.apm.core.logger.Progress; import com.cognifide.apm.core.progress.ProgressImpl; import com.cognifide.apm.core.services.version.VersionService; import com.cognifide.apm.core.utils.InstanceTypeProvider; import com.google.common.collect.Maps; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.jackrabbit.api.JackrabbitSession; import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.ResourceResolver; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component( immediate = true, service = {ScriptManager.class, ExtendedScriptManager.class}, property = { Property.DESCRIPTION + "CQSM Script Manager Service", Property.VENDOR } ) public class ScriptManagerImpl implements ExtendedScriptManager { private static final Logger LOG = LoggerFactory.getLogger(ScriptManagerImpl.class); @Reference private ActionFactory actionFactory; @Reference private ScriptStorage scriptStorage; @Reference private ScriptFinder scriptFinder; @Reference private InstanceTypeProvider instanceTypeProvider; @Reference private VersionService versionService; @Reference private History history; @Reference( cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, service = DefinitionsProvider.class ) private final Set<DefinitionsProvider> definitionsProviders = new CopyOnWriteArraySet<>(); private final EventManager eventManager = new EventManager(); private Progress execute(Script script, final ExecutionMode mode, Map<String, String> customDefinitions, ResourceResolver resolver) throws ExecutionException, RepositoryException { if (script == null) { throw new ExecutionException("Script is not specified"); } if (mode == null) { throw new ExecutionException("Execution mode is not specified"); } final String path = script.getPath(); LOG.info(String.format("Script execution started: %s [%s]", path, mode)); final Progress progress = new ProgressImpl(resolver.getUserID()); final ActionExecutor actionExecutor = createExecutor(mode, resolver); final Context context = actionExecutor.getContext(); final SessionSavingPolicy savingPolicy = context.getSavingPolicy(); eventManager.trigger(Event.BEFORE_EXECUTE, script, mode, progress); ScriptRunner scriptRunner = new ScriptRunner(scriptFinder, resolver, mode == ExecutionMode.VALIDATION, (executionContext, commandName, arguments) -> { try { context.setCurrentAuthorizable(executionContext.getAuthorizable()); ActionDescriptor descriptor = actionFactory.evaluate(commandName, arguments); ActionResult result = actionExecutor.execute(descriptor); executionContext.setAuthorizable(context.getCurrentAuthorizableIfExists()); progress.addEntry(descriptor, result); if ((Status.ERROR != result.getStatus()) || (ExecutionMode.DRY_RUN == mode)) { savingPolicy.save(context.getSession(), SessionSavingMode.EVERY_ACTION); } } catch (RepositoryException | ActionCreationException e) { LOG.error("Error while processing command: {}", commandName, e); progress.addEntry(Status.ERROR, e.getMessage(), commandName); } }); try { Map<String, String> definitions = new HashMap<>(); definitions.putAll(getPredefinedDefinitions()); definitions.putAll(customDefinitions); scriptRunner.execute(script, progress, definitions); } catch (RuntimeException e) { progress.addEntry(Status.ERROR, e.getMessage()); } if (progress.isSuccess()) { savingPolicy.save(context.getSession(), SessionSavingMode.SINGLE); } return progress; } @Override public synchronized Progress process(final Script script, final ExecutionMode mode, ResourceResolver resolver) throws RepositoryException, PersistenceException { return process(script, mode, Maps.newHashMap(), resolver); } @Override public Progress process(Script script, final ExecutionMode mode, final Map<String, String> customDefinitions, ResourceResolver resolver) throws RepositoryException, PersistenceException { Progress progress; try { progress = execute(script, mode, customDefinitions, resolver); } catch (ExecutionException e) { progress = new ProgressImpl(resolver.getUserID()); progress.addEntry(Status.ERROR, e.getMessage()); } updateScriptProperties(script, mode, progress.isSuccess()); versionService.updateVersionIfNeeded(resolver, script); saveHistory(script, mode, progress); eventManager.trigger(Event.AFTER_EXECUTE, script, mode, progress); return progress; } private void saveHistory(Script script, ExecutionMode mode, Progress progress) { if (instanceTypeProvider.isOnAuthor()) { if (mode != ExecutionMode.VALIDATION) { history.logLocal(script, mode, progress); } } else { if (mode.isRun()) { try { HistoryEntry entry = history.logLocal(script, mode, progress); history.replicate(entry, progress.getExecutor()); } catch (RepositoryException e) { LOG.error("Repository error occurred while replicating script execution", e); } } } } private void updateScriptProperties(final Script script, final ExecutionMode mode, final boolean success) throws PersistenceException { final MutableScriptWrapper mutableScriptWrapper = new MutableScriptWrapper(script); if (Arrays.asList(ExecutionMode.RUN, ExecutionMode.AUTOMATIC_RUN).contains(mode)) { mutableScriptWrapper.setExecuted(true); } if (ExecutionMode.VALIDATION.equals(mode)) { mutableScriptWrapper.setValid(success); } } @Override public Map<String, String> getPredefinedDefinitions() { Map<String, String> predefinedDefinitions = new HashMap<>(); definitionsProviders.forEach(provider -> predefinedDefinitions.putAll(provider.getPredefinedDefinitions())); return predefinedDefinitions; } @Override public EventManager getEventManager() { return eventManager; } private ActionExecutor createExecutor(ExecutionMode mode, ResourceResolver resolver) throws RepositoryException { final Context context = new ContextImpl((JackrabbitSession) resolver.adaptTo(Session.class)); return ActionExecutorFactory.create(mode, context, actionFactory); } }