/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.
*/
package com.asakusafw.yaess.core;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An base interface of execution script which represents each atomic execution in YAESS.
* @since 0.2.3
* @see ExecutionScriptHandler
*/
public interface ExecutionScript {
/**
* An environment variable name of Asakusa installation home.
*/
String ENV_ASAKUSA_HOME = "ASAKUSA_HOME";
/**
* A placeholder name of the Asakusa installed location.
*/
String PLACEHOLDER_HOME = "{{{PLACEHOLDER/ASAKUSA_HOME}}}";
/**
* A placeholder name of the execution ID.
*/
String PLACEHOLDER_EXECUTION_ID = "{{{PLACEHOLDER/EXECUTION_ID}}}";
/**
* A placeholder name of the execution arguments.
*/
String PLACEHOLDER_ARGUMENTS = "{{{PLACEHOLDER/ARGUMENTS}}}";
/**
* Returns the kind of this script.
* @return the kind of this script
*/
Kind getKind();
/**
* Returns the ID of this script execution.
* @return the ID of this script execution
*/
String getId();
/**
* Returns the IDs representing blockers of this script execution.
* @return blockers' ID
*/
Set<String> getBlockerIds();
/**
* Returns desired environment variables used in this execution.
* @return desired environment variables
*/
Map<String, String> getEnvironmentVariables();
/**
* Returns the supported extension names.
* @return the supported extension names
* @since 0.8.0
*/
Set<String> getSupportedExtensions();
/**
* Returns whether this script is resolved.
* @return {@code true} for resolved instance, otherwise {@code false}
* @see #resolve(ExecutionContext, ExecutionScriptHandler)
*/
boolean isResolved();
/**
* Resolves placeholders defined in this script.
* This method does not change this object, but returns a resolved object.
* @param context current context
* @param handler target handler
* @return resolved object
* @throws InterruptedException if interrupted while resolving this object
* @throws IOException if failed to resolve some entry
* @throws IllegalArgumentException if some parameters were {@code null}
* @see #isResolved()
*/
ExecutionScript resolve(
ExecutionContext context,
ExecutionScriptHandler<?> handler) throws InterruptedException, IOException;
/**
* Type of each {@link ExecutionScript}.
* @since 0.2.3
*/
enum Kind implements Symbolic {
/**
* Generic command script.
*/
COMMAND,
/**
* Script corresponding to Hadoop.
*/
HADOOP,
;
/**
* Returns the symbol of this phase.
* This symbol is used in {@link ExecutionScript}s.
* @return the symbol of this phase
*/
@Override
public String getSymbol() {
return name().toLowerCase();
}
/**
* Returns an {@link Kind} corresponded to the symbol.
* @param symbol target symbol
* @return the corresponding phase, or {@code null} if not found
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public static Kind findFromSymbol(String symbol) {
if (symbol == null) {
throw new IllegalArgumentException("symbol must not be null"); //$NON-NLS-1$
}
return Lazy.SYMBOLS.get(symbol);
}
private static final class Lazy {
static final Map<String, Kind> SYMBOLS;
static {
Map<String, Kind> map = new HashMap<>();
for (Kind phase : values()) {
map.put(phase.getSymbol(), phase);
}
SYMBOLS = Collections.unmodifiableMap(map);
}
private Lazy() {
return;
}
}
}
/**
* Resolves placeholders in script.
* @since 0.2.3
*/
class PlaceholderResolver {
static final Logger LOG = LoggerFactory.getLogger(ExecutionScript.class);
private static final Pattern PLACEHOLDERS = Pattern.compile(
Pattern.quote(PLACEHOLDER_HOME) + '|'
+ Pattern.quote(PLACEHOLDER_EXECUTION_ID) + '|'
+ Pattern.quote(PLACEHOLDER_ARGUMENTS));
private final Map<String, String> replacements;
/**
* Creates a new instance.
* @param script target script
* @param context current execution context
* @param handler handler which will attempt to resolve placeholders
* @throws InterruptedException if interrupted to prepare this resolver
* @throws IOException if failed to prepare replacements
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public PlaceholderResolver(
ExecutionScript script,
ExecutionContext context,
ExecutionScriptHandler<?> handler) throws InterruptedException, IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
if (context == null) {
throw new IllegalArgumentException("context must not be null"); //$NON-NLS-1$
}
if (handler == null) {
throw new IllegalArgumentException("handler must not be null"); //$NON-NLS-1$
}
replacements = new HashMap<>();
replacements.put(PLACEHOLDER_HOME, getAsakusaHomePath(context, script, handler));
replacements.put(PLACEHOLDER_EXECUTION_ID, context.getExecutionId());
replacements.put(PLACEHOLDER_ARGUMENTS, context.getArgumentsAsString());
}
private String getAsakusaHomePath(
ExecutionContext context,
ExecutionScript script,
ExecutionScriptHandler<?> handler) throws IOException, InterruptedException {
assert context != null;
assert script != null;
assert handler != null;
String inScript = script.getEnvironmentVariables().get(ENV_ASAKUSA_HOME);
if (inScript != null && inScript.equals(PLACEHOLDER_HOME) == false) {
LOG.debug("Asakusa location is found in script: {} -> {}",
script.getId(),
inScript);
return inScript;
}
Map<String, String> environmentVariables = handler.getEnvironmentVariables(context, script);
String inHandler = environmentVariables.get(ENV_ASAKUSA_HOME);
if (inHandler != null) {
LOG.debug("Asakusa location is found in handler: {} -> {}",
script.getId(),
inHandler);
return inHandler;
}
throw new IOException(MessageFormat.format(
"{0} is not defined for \"{1}\"",
ENV_ASAKUSA_HOME,
handler.getHandlerId()));
}
/**
* Resolves placeholder expressions in the target string.
* @param target target string
* @return the resolved string
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public String resolve(String target) {
if (target == null) {
throw new IllegalArgumentException("target must not be null"); //$NON-NLS-1$
}
Matcher matcher = PLACEHOLDERS.matcher(target);
StringBuilder buf = new StringBuilder();
int start = 0;
while (matcher.find(start)) {
buf.append(target.substring(start, matcher.start()));
String replacement = replacements.get(matcher.group());
assert replacement != null;
buf.append(replacement);
start = matcher.end();
}
buf.append(target.substring(start));
return buf.toString();
}
}
}