blob: 625c83c2a0d92d25f96a53411b151f86775b432b [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.openapi.externalSystem.service;
import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings;
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver;
import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Denis Zhdanov
* @since 8/9/13 4:28 PM
*/
public class RemoteExternalSystemFacadeImpl<S extends ExternalSystemExecutionSettings> extends AbstractExternalSystemFacadeImpl<S> {
private static final long DEFAULT_REMOTE_PROCESS_TTL_IN_MS = TimeUnit.MILLISECONDS.convert(3, TimeUnit.MINUTES);
private final AtomicInteger myCallsInProgressNumber = new AtomicInteger();
private final Alarm myShutdownAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private final AtomicLong myTtlMs = new AtomicLong(DEFAULT_REMOTE_PROCESS_TTL_IN_MS);
private volatile boolean myStdOutputConfigured;
public RemoteExternalSystemFacadeImpl(@NotNull Class<ExternalSystemProjectResolver<S>> projectResolverClass,
@NotNull Class<ExternalSystemTaskManager<S>> buildManagerClass)
throws IllegalAccessException, InstantiationException
{
super(projectResolverClass, buildManagerClass);
updateAutoShutdownTime();
}
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
if (args.length < 1) {
throw new IllegalArgumentException(
"Can't create external system facade. Reason: given arguments don't contain information about external system resolver to use");
}
final Class<ExternalSystemProjectResolver<?>> resolverClass = (Class<ExternalSystemProjectResolver<?>>)Class.forName(args[0]);
if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) {
throw new IllegalArgumentException(String.format(
"Can't create external system facade. Reason: given external system resolver class (%s) must be IS-A '%s'",
resolverClass,
ExternalSystemProjectResolver.class));
}
if (args.length < 2) {
throw new IllegalArgumentException(
"Can't create external system facade. Reason: given arguments don't contain information about external system build manager to use"
);
}
final Class<ExternalSystemTaskManager<?>> buildManagerClass = (Class<ExternalSystemTaskManager<?>>)Class.forName(args[1]);
if (!ExternalSystemProjectResolver.class.isAssignableFrom(resolverClass)) {
throw new IllegalArgumentException(String.format(
"Can't create external system facade. Reason: given external system build manager (%s) must be IS-A '%s'",
buildManagerClass, ExternalSystemTaskManager.class
));
}
// running the code indicates remote communication mode with external system
Registry.get(
System.getProperty(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY) +
ExternalSystemConstants.USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY_SUFFIX).setValue(false);
RemoteExternalSystemFacadeImpl facade = new RemoteExternalSystemFacadeImpl(resolverClass, buildManagerClass);
facade.init();
start(facade);
}
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed", "unchecked", "UseOfSystemOutOrSystemErr"})
@Override
protected <I extends RemoteExternalSystemService<S>, C extends I> I createService(@NotNull Class<I> interfaceClass, @NotNull final C impl)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, RemoteException
{
if (!myStdOutputConfigured) {
myStdOutputConfigured = true;
System.setOut(new LineAwarePrintStream(System.out));
System.setErr(new LineAwarePrintStream(System.err));
}
I proxy = (I)Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myCallsInProgressNumber.incrementAndGet();
try {
return method.invoke(impl, args);
}
finally {
myCallsInProgressNumber.decrementAndGet();
updateAutoShutdownTime();
}
}
});
return (I)UnicastRemoteObject.exportObject(proxy, 0);
}
@Override
public void applySettings(@NotNull S settings) throws RemoteException {
super.applySettings(settings);
long ttl = settings.getRemoteProcessIdleTtlInMs();
if (ttl > 0) {
myTtlMs.set(ttl);
}
}
/**
* Schedules automatic process termination in {@code #REMOTE_GRADLE_PROCESS_TTL_IN_MS} milliseconds.
* <p/>
* Rationale: it's possible that IJ user performs gradle related activity (e.g. import from gradle) when the works purely
* at IJ. We don't want to keep remote process that communicates with the gradle api then.
*/
private void updateAutoShutdownTime() {
myShutdownAlarm.cancelAllRequests();
myShutdownAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (myCallsInProgressNumber.get() > 0) {
updateAutoShutdownTime();
return;
}
System.exit(0);
}
}, (int)myTtlMs.get());
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
private static class LineAwarePrintStream extends PrintStream {
private LineAwarePrintStream(@NotNull final PrintStream delegate) {
super(new OutputStream() {
@NotNull private final StringBuilder myBuffer = new StringBuilder();
@Override
public void write(int b) throws IOException {
char c = (char)b;
myBuffer.append(Character.toString(c));
if (c == '\n') {
doFlush();
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
int start = off;
int maxOffset = off + len;
for (int i = off; i < maxOffset; i++) {
if (b[i] == '\n') {
myBuffer.append(new String(b, start, i - start + 1));
doFlush();
start = i + 1;
}
}
if (start < maxOffset) {
myBuffer.append(new String(b, start, maxOffset - start));
}
}
private void doFlush() {
delegate.print(myBuffer.toString());
delegate.flush();
myBuffer.setLength(0);
}
});
}
}
}