/**
* 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.windgate.core;
import java.io.Closeable;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.runtime.core.context.RuntimeContext;
import com.asakusafw.runtime.core.context.SimulationSupport;
import com.asakusafw.windgate.core.process.ProcessProfile;
import com.asakusafw.windgate.core.process.ProcessProvider;
import com.asakusafw.windgate.core.resource.DriverRepository;
import com.asakusafw.windgate.core.resource.ResourceMirror;
import com.asakusafw.windgate.core.resource.ResourceProfile;
import com.asakusafw.windgate.core.resource.ResourceProvider;
import com.asakusafw.windgate.core.session.SessionMirror;
import com.asakusafw.windgate.core.session.SessionProfile;
import com.asakusafw.windgate.core.session.SessionProvider;
import com.asakusafw.windgate.core.util.SafeCloser;
/**
* Executes WindGate.
* @since 0.2.2
* @version 0.4.0
*/
@SimulationSupport
public class GateTask implements Closeable {
static final WindGateLogger WGLOG = new WindGateCoreLogger(GateTask.class);
static final Logger LOG = LoggerFactory.getLogger(GateTask.class);
private final ExecutorService executor;
private final SessionProvider sessionProvider;
private final List<ResourceProvider> resourceProviders;
private final Map<String, ProcessProvider> processProviders;
final GateProfile profile;
final GateScript script;
final String sessionId;
private final boolean createSession;
private final boolean completeSession;
private final ParameterList arguments;
/**
* Creates a new instance.
* @param profile the gate profile
* @param script the gate script
* @param sessionId current session ID
* @param createSession {@code true} to create a new session, otherwise {@code false}
* @param completeSession {@code true} to complete the session, otherwise {@code false}
* @param arguments execution arguments (argument name => value)
* @throws IOException if failed to initialize the task
* @throws IllegalArgumentException if any parameter is {@code null}
*/
public GateTask(
GateProfile profile,
GateScript script,
String sessionId,
boolean createSession,
boolean completeSession,
ParameterList arguments) throws IOException {
if (profile == null) {
throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$
}
if (script == null) {
throw new IllegalArgumentException("kind must not be null"); //$NON-NLS-1$
}
if (sessionId == null) {
throw new IllegalArgumentException("kind must not be null"); //$NON-NLS-1$
}
if (arguments == null) {
throw new IllegalArgumentException("arguments must not be null"); //$NON-NLS-1$
}
this.profile = profile;
this.script = script;
this.sessionId = sessionId;
this.createSession = createSession;
this.completeSession = completeSession;
this.arguments = arguments;
this.sessionProvider = loadSessionProvider(profile.getSession());
this.resourceProviders = loadResourceProviders(profile.getResources());
this.processProviders = loadProcessProviders(profile.getProcesses());
this.executor = Executors.newFixedThreadPool(profile.getCore().getMaxProcesses(), new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, String.format("WindGate-%d", counter.incrementAndGet()));
t.setDaemon(true);
return t;
}
});
}
private SessionProvider loadSessionProvider(SessionProfile session) throws IOException {
assert session != null;
LOG.debug("Loading session provider: {}",
session.getProviderClass().getName());
SessionProvider result = session.createProvider();
return result;
}
private List<ResourceProvider> loadResourceProviders(
List<ResourceProfile> resources) throws IOException {
assert resources != null;
List<ResourceProvider> results = new ArrayList<>();
for (ResourceProfile resourceProfile : resources) {
LOG.debug("Loading resource provider \"{}\": {}",
resourceProfile.getName(),
resourceProfile.getProviderClass().getName());
ResourceProvider provider = resourceProfile.createProvider();
results.add(provider);
}
return results;
}
private Map<String, ProcessProvider> loadProcessProviders(
List<ProcessProfile> processes) throws IOException {
assert processes != null;
Map<String, ProcessProvider> results = new TreeMap<>();
for (ProcessProfile processProfile : processes) {
LOG.debug("Loading process provider \"{}\": {}",
processProfile.getName(),
processProfile.getProviderClass().getName());
assert results.containsKey(processProfile.getName()) == false;
ProcessProvider provider = processProfile.createProvider();
results.put(processProfile.getName(), provider);
}
return results;
}
/**
* Executes WindGate process.
* @throws IOException if failed
* @throws InterruptedException if interrupted
*/
public void execute() throws IOException, InterruptedException {
WGLOG.info("I00000",
sessionId,
profile.getName(),
script.getName());
long start = System.currentTimeMillis();
WGLOG.info("I00001",
sessionId,
profile.getName(),
script.getName());
try {
try (SafeCloser<SessionMirror> session = new SafeCloser<SessionMirror>() {
@Override
protected void handle(IOException exception) throws IOException {
WGLOG.warn(exception, "W00001",
sessionId,
profile.getName(),
script.getName());
}
}) {
session.set(attachSession(createSession));
WGLOG.info("I00002",
sessionId,
profile.getName(),
script.getName());
List<ResourceMirror> resources = createResources();
if (createSession) {
WGLOG.info("I00003",
sessionId,
profile.getName(),
script.getName());
fireSessionCreated(resources);
}
WGLOG.info("I00004",
sessionId,
profile.getName(),
script.getName());
prepareResources(resources);
WGLOG.info("I00005",
sessionId,
profile.getName(),
script.getName());
runGateProcesses(resources);
if (completeSession) {
WGLOG.info("I00006",
sessionId,
profile.getName(),
script.getName());
fireSessionCompleted(resources);
WGLOG.info("I00007",
sessionId,
profile.getName(),
script.getName());
session.get().complete();
}
}
WGLOG.info("I00008",
sessionId,
profile.getName(),
script.getName());
} finally {
long end = System.currentTimeMillis();
WGLOG.info("I00999",
sessionId,
profile.getName(),
script.getName(),
end - start);
}
}
private SessionMirror attachSession(boolean create) throws IOException {
if (create) {
LOG.debug("Creating session: {}",
sessionId);
if (RuntimeContext.get().canExecute(sessionProvider)) {
return sessionProvider.create(sessionId);
} else {
return new SessionMirror.Null(sessionId);
}
} else {
LOG.debug("Opening session: {}",
sessionId);
if (RuntimeContext.get().canExecute(sessionProvider)) {
return sessionProvider.open(sessionId);
} else {
return new SessionMirror.Null(sessionId);
}
}
}
private List<ResourceMirror> createResources() throws IOException {
List<ResourceMirror> results = new ArrayList<>();
for (ResourceProvider provider : resourceProviders) {
LOG.debug("Creating resource: {}",
provider.getClass().getName());
// NOTE each initialization can be done in multi-threaded
ResourceMirror resource = provider.create(sessionId, arguments);
results.add(resource);
}
return results;
}
private void fireSessionCreated(List<ResourceMirror> resources) throws IOException {
assert resources != null;
for (ResourceMirror resource : resources) {
if (resource.isTransactional()) {
try {
LOG.debug("Initializing transactional resource \"{}\" for session \"{}\"",
resource.getName(),
sessionId);
if (RuntimeContext.get().canExecute(resource)) {
resource.onSessionCreated();
}
} catch (IOException e) {
WGLOG.error(e, "E00001",
sessionId,
profile.getName(),
script.getName(),
resource.getName());
throw new IOException(MessageFormat.format(
"Failed to initializing resource \"{0}\" (session={1})",
resource.getName(),
sessionId), e);
}
}
}
LinkedList<Future<?>> futures = new LinkedList<>();
for (ResourceMirror resource : resources) {
if (resource.isTransactional() == false) {
Future<?> future = executor.submit(() -> {
LOG.debug("Initializing resource \"{}\" for session \"{}\"",
resource.getName(),
sessionId);
try {
if (RuntimeContext.get().canExecute(resource)) {
resource.onSessionCreated();
}
} catch (IOException e) {
WGLOG.error(e, "E00001",
sessionId,
profile.getName(),
script.getName(),
resource.getName());
throw new IOException(MessageFormat.format(
"Failed to initializing resource \"{0}\" (session={1})",
resource.getName(),
sessionId), e);
}
return null;
});
futures.add(future);
}
}
int failureCount = waitForComplete(futures);
if (failureCount > 0) {
throw new IOException(MessageFormat.format(
"Failed to initialize session: {0}",
sessionId));
}
}
private void prepareResources(List<ResourceMirror> resources) throws IOException {
assert resources != null;
LinkedList<Future<?>> futures = new LinkedList<>();
for (ResourceMirror resource : resources) {
Future<?> future = executor.submit(() -> {
LOG.debug("Preparing resource \"{}\"",
resource.getName());
try {
if (RuntimeContext.get().canExecute(resource)) {
resource.prepare(script);
}
} catch (IOException e) {
WGLOG.error(e, "E00002",
sessionId,
profile.getName(),
script.getName(),
resource.getName());
throw new IOException(MessageFormat.format(
"Preparing {0} failed (session={1})",
resource.getName(),
sessionId), e);
}
return null;
});
futures.add(future);
}
int failureCount = waitForComplete(futures);
if (failureCount > 0) {
throw new IOException("Failed to prepare some resources");
}
}
private void runGateProcesses(List<ResourceMirror> resources) throws IOException {
assert resources != null;
DriverRepository drivers = new DriverRepository(resources);
LinkedList<Future<?>> futures = new LinkedList<>();
for (ProcessScript<?> process : script.getProcesses()) {
ProcessProvider processProvider = processProviders.get(process.getProcessType());
assert processProvider != null;
Future<?> future = executor.submit(() -> {
LOG.debug("Starting gate process: {}",
process.getName(),
sessionId);
try {
if (RuntimeContext.get().canExecute(processProvider)) {
processProvider.execute(drivers, process);
} else {
LOG.info("Skipped process execution (simulated)");
}
} catch (IOException e) {
WGLOG.error(e, "E00003",
sessionId,
profile.getName(),
script.getName(),
process.getName());
throw new IOException(MessageFormat.format(
"Process {0} failed: source={1} -> drain={2} (session={3})",
process.getName(),
process.getSourceScript().getResourceName(),
process.getDrainScript().getResourceName(),
sessionId), e);
}
return null;
});
futures.add(future);
}
int failureCount = waitForComplete(futures);
if (failureCount > 0) {
throw new IOException(MessageFormat.format(
"Failed to execute some gate processes in session \"{0}\"",
sessionId));
}
}
private void fireSessionCompleted(List<ResourceMirror> resources) throws IOException {
assert resources != null;
LinkedList<Future<?>> futures = new LinkedList<>();
for (ResourceMirror resource : resources) {
if (resource.isTransactional() == false) {
Future<?> future = executor.submit(() -> {
LOG.debug("Finalizing resource \"{}\" for session \"{}\"",
resource.getName(),
sessionId);
try {
if (RuntimeContext.get().canExecute(resource)) {
resource.onSessionCompleting();
}
} catch (IOException e) {
WGLOG.error(e, "E00004",
sessionId,
profile.getName(),
script.getName(),
resource.getName());
throw new IOException(MessageFormat.format(
"Failed to finalizing resource \"{0}\" (session={1})",
resource.getName(),
sessionId), e);
}
return null;
});
futures.add(future);
}
}
int failureCount = waitForComplete(futures);
if (failureCount > 0) {
throw new IOException(MessageFormat.format(
"Failed to complete session: {0}",
sessionId));
}
for (ResourceMirror resource : resources) {
if (resource.isTransactional()) {
LOG.debug("Finalizing transactional resource \"{}\" for session \"{}\"",
resource.getName(),
sessionId);
try {
if (RuntimeContext.get().canExecute(resource)) {
resource.onSessionCompleting();
}
} catch (IOException e) {
WGLOG.error(e, "E00004",
sessionId,
profile.getName(),
script.getName(),
resource.getName());
throw new IOException(MessageFormat.format(
"Failed to finalizing resource \"{0}\" (session={1})",
resource.getName(),
sessionId), e);
}
}
}
}
private int waitForComplete(LinkedList<Future<?>> futures) {
assert futures != null;
int failureCount = 0;
while (futures.isEmpty() == false) {
Future<?> future = futures.removeFirst();
try {
future.get(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
futures.addLast(future);
// continue
} catch (InterruptedException e) {
WGLOG.warn(e, "W00002",
sessionId,
profile.getName(),
script.getName());
futures.addLast(future);
cancelAll(futures);
// continue
} catch (ExecutionException e) {
failureCount++;
WGLOG.warn(e, "W00003",
sessionId,
profile.getName(),
script.getName());
cancelAll(futures);
if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
}
} catch (CancellationException e) {
failureCount++;
WGLOG.warn(e, "W00004",
sessionId,
profile.getName(),
script.getName());
}
}
return failureCount;
}
private void cancelAll(LinkedList<Future<?>> futures) {
assert futures != null;
for (Future<?> future : futures) {
future.cancel(true);
}
}
@Override
public void close() {
executor.shutdown();
}
}