perf(app): decouple javafx toolkit for fast ccsp startup on demand
This commit is contained in:
+43
-34
@@ -100,37 +100,18 @@ public class CyberCoreSignerApplication {
|
|||||||
logger.error("ALERTA CRÍTICO: Engine criptográfica JCA/JCE não está totalmente operacional!");
|
logger.error("ALERTA CRÍTICO: Engine criptográfica JCA/JCE não está totalmente operacional!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se o ambiente tiver suporte gráfico e não for headless forçado, abre a interface JavaFX
|
// 1. Inicializa o núcleo e o servidor CCSP imediatamente (alta velocidade)
|
||||||
boolean isHeadless = headlessMode || GraphicsEnvironment.isHeadless();
|
|
||||||
|
|
||||||
if (isHeadless) {
|
|
||||||
logger.info("Executando em modo console / headless (sem interface gráfica).");
|
|
||||||
startHeadlessServer();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
logger.info("Iniciando interface gráfica desktop...");
|
|
||||||
CyberCoreDesktopApplication.setDevelopmentMode(developmentMode);
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() ->
|
|
||||||
logger.warn("JVM em encerramento (sinal externo, System.exit ou fim do aplicativo)."),
|
|
||||||
"cybercore-shutdown-diagnostic"));
|
|
||||||
Application.launch(CyberCoreDesktopApplication.class, args);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo console.", t.getMessage());
|
|
||||||
startHeadlessServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("CyberCore Signer finalizado.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startHeadlessServer() {
|
|
||||||
try {
|
|
||||||
CertificateManager certManager = CertificateManager.createDefault();
|
CertificateManager certManager = CertificateManager.createDefault();
|
||||||
if (developmentMode) {
|
if (developmentMode) {
|
||||||
logger.info("Modo de desenvolvimento ativo (com certificados reais do sistema).");
|
logger.info("Modo de desenvolvimento ativo (com certificados reais do sistema).");
|
||||||
}
|
}
|
||||||
SigningService signingService = new SigningService(certManager);
|
SigningService signingService = new SigningService(certManager);
|
||||||
runningServer = new CcspServer(configuredPort, certManager, signingService, br.com.cybercore.signer.core.security.ApplicationRegistry.getInstance(), null);
|
br.com.cybercore.signer.core.protocol.CcspUserPromptListener promptListener =
|
||||||
|
new br.com.cybercore.signer.ui.prompt.JavaFxPromptListener();
|
||||||
|
|
||||||
|
try {
|
||||||
|
runningServer = new CcspServer(configuredPort, certManager, signingService,
|
||||||
|
br.com.cybercore.signer.core.security.ApplicationRegistry.getInstance(), promptListener);
|
||||||
runningServer.start();
|
runningServer.start();
|
||||||
logger.info("Servidor local CCSP/1 pronto em http://127.0.0.1:{}", runningServer.getPort());
|
logger.info("Servidor local CCSP/1 pronto em http://127.0.0.1:{}", runningServer.getPort());
|
||||||
|
|
||||||
@@ -138,22 +119,50 @@ public class CyberCoreSignerApplication {
|
|||||||
logger.info("Encerrando servidor local CCSP/1...");
|
logger.info("Encerrando servidor local CCSP/1...");
|
||||||
stopServer();
|
stopServer();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mantém processo vivo para atender requisições web, a menos que em modo no-wait
|
|
||||||
if (!noWaitMode) {
|
|
||||||
Thread.currentThread().join();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} catch (java.net.BindException e) {
|
} catch (java.net.BindException e) {
|
||||||
logger.error("ALERTA DE COLISÃO DE PORTA: A porta {} já está em uso por outra instância do CyberCore Signer ou outro serviço. Para usar uma porta alternativa, utilize o parâmetro --port <porta> ou defina a variável de ambiente CYBERCORE_PORT.", configuredPort);
|
logger.error("ALERTA DE COLISÃO DE PORTA: A porta {} já está em uso por outra instância do CyberCore Signer ou outro serviço. Para usar uma porta alternativa, utilize o parâmetro --port <porta> ou defina a variável de ambiente CYBERCORE_PORT.", configuredPort);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e.getCause() instanceof java.net.BindException || (e.getMessage() != null && e.getMessage().contains("Address already in use"))) {
|
if (e.getCause() instanceof java.net.BindException || (e.getMessage() != null && e.getMessage().contains("Address already in use"))) {
|
||||||
logger.error("ALERTA DE COLISÃO DE PORTA: A porta {} já está em uso por outra instância do CyberCore Signer ou outro serviço. Para usar uma porta alternativa, utilize o parâmetro --port <porta> ou defina a variável de ambiente CYBERCORE_PORT.", configuredPort);
|
logger.error("ALERTA DE COLISÃO DE PORTA: A porta {} já está em uso por outra instância do CyberCore Signer ou outro serviço. Para usar uma porta alternativa, utilize o parâmetro --port <porta> ou defina a variável de ambiente CYBERCORE_PORT.", configuredPort);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Falha ao inicializar servidor local CCSP/1 no modo console: {}", e.getMessage(), e);
|
logger.error("Falha ao inicializar servidor local CCSP/1: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Se o ambiente tiver suporte gráfico e não for headless forçado, abre a interface JavaFX
|
||||||
|
boolean isHeadless = headlessMode || GraphicsEnvironment.isHeadless();
|
||||||
|
|
||||||
|
if (!isHeadless) {
|
||||||
|
try {
|
||||||
|
logger.info("Iniciando interface gráfica desktop desacoplada...");
|
||||||
|
CyberCoreDesktopApplication.setDevelopmentMode(developmentMode);
|
||||||
|
CyberCoreDesktopApplication.setSharedServices(runningServer, certManager, signingService);
|
||||||
|
Thread uiThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Application.launch(CyberCoreDesktopApplication.class, args);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn("Interface gráfica finalizada ou indisponível: {}", t.getMessage());
|
||||||
|
}
|
||||||
|
}, "cybercore-desktop-ui");
|
||||||
|
uiThread.setDaemon(false);
|
||||||
|
uiThread.start();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo serviço.", t.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Executando em modo console / headless (sem interface gráfica).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mantém processo vivo para atender requisições web, a menos que em modo no-wait
|
||||||
|
if (!noWaitMode) {
|
||||||
|
try {
|
||||||
|
Thread.currentThread().join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("CyberCore Signer finalizado.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void parseArguments(String[] args) {
|
public static void parseArguments(String[] args) {
|
||||||
|
|||||||
@@ -34,4 +34,17 @@ class ArchitectureTest {
|
|||||||
|
|
||||||
rule.check(importedClasses);
|
rule.check(importedClasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("O módulo Core não deve acessar o ciclo de vida do toolkit JavaFX (Application ou Platform)")
|
||||||
|
void coreShouldNotAccessJavaFxLifecycle() {
|
||||||
|
ArchRule rule = noClasses()
|
||||||
|
.that().resideInAPackage("br.com.cybercore.signer.core..")
|
||||||
|
.should().dependOnClassesThat()
|
||||||
|
.haveFullyQualifiedName("javafx.application.Application")
|
||||||
|
.orShould().dependOnClassesThat()
|
||||||
|
.haveFullyQualifiedName("javafx.application.Platform");
|
||||||
|
|
||||||
|
rule.check(importedClasses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-3
@@ -75,12 +75,22 @@ public class CyberCoreDesktopApplication extends Application {
|
|||||||
private static boolean developmentMode = false;
|
private static boolean developmentMode = false;
|
||||||
private static MainAppView mainAppView;
|
private static MainAppView mainAppView;
|
||||||
|
|
||||||
|
private static volatile CcspServer sharedCcspServer = null;
|
||||||
|
private static volatile CertificateManager sharedCertManager = null;
|
||||||
|
private static volatile SigningService sharedSigningService = null;
|
||||||
|
|
||||||
private CertificateManager certificateManager;
|
private CertificateManager certificateManager;
|
||||||
private SigningService signingService;
|
private SigningService signingService;
|
||||||
private CcspServer ccspServer;
|
private CcspServer ccspServer;
|
||||||
private SystemTrayManager systemTrayManager;
|
private SystemTrayManager systemTrayManager;
|
||||||
private SmartCardWatcher smartCardWatcher;
|
private SmartCardWatcher smartCardWatcher;
|
||||||
|
|
||||||
|
public static void setSharedServices(CcspServer server, CertificateManager certManager, SigningService signingService) {
|
||||||
|
sharedCcspServer = server;
|
||||||
|
sharedCertManager = certManager;
|
||||||
|
sharedSigningService = signingService;
|
||||||
|
}
|
||||||
|
|
||||||
public static void setDevelopmentMode(boolean devMode) {
|
public static void setDevelopmentMode(boolean devMode) {
|
||||||
developmentMode = devMode;
|
developmentMode = devMode;
|
||||||
}
|
}
|
||||||
@@ -93,10 +103,12 @@ public class CyberCoreDesktopApplication extends Application {
|
|||||||
public void start(Stage primaryStage) {
|
public void start(Stage primaryStage) {
|
||||||
logger.info("Iniciando interface gráfica JavaFX...");
|
logger.info("Iniciando interface gráfica JavaFX...");
|
||||||
Platform.setImplicitExit(false);
|
Platform.setImplicitExit(false);
|
||||||
|
br.com.cybercore.signer.ui.prompt.JavaFxPromptListener.markToolkitInitialized();
|
||||||
|
br.com.cybercore.signer.ui.prompt.JavaFxPromptListener.setActiveStage(primaryStage);
|
||||||
|
|
||||||
// Inicializa serviços centrais reais da plataforma
|
// Inicializa ou reutiliza serviços centrais reais da plataforma
|
||||||
this.certificateManager = CertificateManager.createDefault();
|
this.certificateManager = (sharedCertManager != null) ? sharedCertManager : CertificateManager.createDefault();
|
||||||
this.signingService = new SigningService(this.certificateManager);
|
this.signingService = (sharedSigningService != null) ? sharedSigningService : new SigningService(this.certificateManager);
|
||||||
|
|
||||||
if (developmentMode) {
|
if (developmentMode) {
|
||||||
logger.info("Modo de desenvolvimento ativo (utilizando certificados reais da máquina).");
|
logger.info("Modo de desenvolvimento ativo (utilizando certificados reais da máquina).");
|
||||||
@@ -419,6 +431,12 @@ public class CyberCoreDesktopApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initCcspServer(Stage stage) {
|
private void initCcspServer(Stage stage) {
|
||||||
|
if (sharedCcspServer != null) {
|
||||||
|
this.ccspServer = sharedCcspServer;
|
||||||
|
logger.info("Vinculada interface desktop ao servidor CCSP/1 já ativo na porta {}", this.ccspServer.getPort());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CcspUserPromptListener promptListener = new CcspUserPromptListener() {
|
CcspUserPromptListener promptListener = new CcspUserPromptListener() {
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> requestApplicationAuthorization(String origin, String appName, String applicationId) {
|
public CompletableFuture<Boolean> requestApplicationAuthorization(String origin, String appName, String applicationId) {
|
||||||
|
|||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
package br.com.cybercore.signer.ui.prompt;
|
||||||
|
|
||||||
|
import br.com.cybercore.signer.core.domain.ManagedCertificate;
|
||||||
|
import br.com.cybercore.signer.core.domain.SigningContext;
|
||||||
|
import br.com.cybercore.signer.core.protocol.CcspRequest;
|
||||||
|
import br.com.cybercore.signer.core.protocol.CcspUserPromptListener;
|
||||||
|
import br.com.cybercore.signer.core.security.ApplicationRegistry;
|
||||||
|
import br.com.cybercore.signer.core.security.AuthorizedApplication;
|
||||||
|
import br.com.cybercore.signer.ui.view.ApplicationApprovalDialog;
|
||||||
|
import br.com.cybercore.signer.ui.view.SignaturePromptDialog;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener desacoplado de prompts do CCSP para a interface JavaFX.
|
||||||
|
* Garante que o toolkit JavaFX só seja inicializado sob demanda (on-demand)
|
||||||
|
* quando houver um diálogo visual de confirmação a ser exibido ao usuário.
|
||||||
|
*/
|
||||||
|
public class JavaFxPromptListener implements CcspUserPromptListener {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JavaFxPromptListener.class);
|
||||||
|
private static final AtomicBoolean FX_TOOLKIT_INITIALIZED = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private static volatile Stage activeStage = null;
|
||||||
|
|
||||||
|
public static void setActiveStage(Stage stage) {
|
||||||
|
activeStage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void markToolkitInitialized() {
|
||||||
|
FX_TOOLKIT_INITIALIZED.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runOnFxThread(Runnable action) {
|
||||||
|
if (FX_TOOLKIT_INITIALIZED.get()) {
|
||||||
|
Platform.runLater(action);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Platform.startup(() -> {
|
||||||
|
FX_TOOLKIT_INITIALIZED.set(true);
|
||||||
|
Platform.setImplicitExit(false);
|
||||||
|
action.run();
|
||||||
|
});
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
FX_TOOLKIT_INITIALIZED.set(true);
|
||||||
|
Platform.runLater(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> requestApplicationAuthorization(String origin, String appName, String applicationId) {
|
||||||
|
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||||
|
runOnFxThread(() -> {
|
||||||
|
try {
|
||||||
|
ApplicationApprovalDialog dialog = new ApplicationApprovalDialog(activeStage, origin, appName, applicationId);
|
||||||
|
dialog.showAndAwait().whenComplete((approved, err) -> {
|
||||||
|
if (err != null) {
|
||||||
|
future.complete(false);
|
||||||
|
} else {
|
||||||
|
future.complete(Boolean.TRUE.equals(approved));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Erro ao exibir diálogo de autorização de aplicação: {}", ex.getMessage());
|
||||||
|
future.complete(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> requestSignatureConfirmation(CcspRequest request, ManagedCertificate certificate) {
|
||||||
|
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||||
|
runOnFxThread(() -> {
|
||||||
|
try {
|
||||||
|
byte[] digestBytes;
|
||||||
|
if (request.digest() != null && !request.digest().isBlank()) {
|
||||||
|
digestBytes = Base64.getDecoder().decode(request.digest());
|
||||||
|
} else if (request.pdf() != null && !request.pdf().isBlank()) {
|
||||||
|
byte[] rawPdf = Base64.getDecoder().decode(request.pdf());
|
||||||
|
digestBytes = MessageDigest.getInstance("SHA-256").digest(rawPdf);
|
||||||
|
} else if (request.challenge() != null && !request.challenge().isBlank()) {
|
||||||
|
digestBytes = MessageDigest.getInstance("SHA-256")
|
||||||
|
.digest(request.challenge().getBytes(StandardCharsets.UTF_8));
|
||||||
|
} else {
|
||||||
|
digestBytes = new byte[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
SigningContext context = new SigningContext(
|
||||||
|
request.requestId(),
|
||||||
|
request.applicationId(),
|
||||||
|
digestBytes,
|
||||||
|
request.algorithm() != null ? request.algorithm() : "SHA256withRSA",
|
||||||
|
certificate.id(),
|
||||||
|
Instant.now()
|
||||||
|
);
|
||||||
|
|
||||||
|
String appName = ApplicationRegistry.getInstance().findById(request.applicationId())
|
||||||
|
.map(AuthorizedApplication::name)
|
||||||
|
.orElse(request.applicationId());
|
||||||
|
|
||||||
|
Long docSize = request.documentSize();
|
||||||
|
if (docSize == null && request.pdf() != null) {
|
||||||
|
try {
|
||||||
|
docSize = (long) Base64.getDecoder().decode(request.pdf()).length;
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
} else if (docSize == null && request.xmlBase64() != null) {
|
||||||
|
try {
|
||||||
|
docSize = (long) Base64.getDecoder().decode(request.xmlBase64()).length;
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String requestOrigin = request.origin();
|
||||||
|
if (requestOrigin == null || requestOrigin.isBlank()) {
|
||||||
|
requestOrigin = ApplicationRegistry.getInstance().findById(request.applicationId())
|
||||||
|
.map(a -> String.join(", ", a.allowedOrigins()))
|
||||||
|
.orElse("Requisição Web Local");
|
||||||
|
}
|
||||||
|
|
||||||
|
SignaturePromptDialog dialog = new SignaturePromptDialog(
|
||||||
|
activeStage, appName, requestOrigin, certificate, context,
|
||||||
|
request.documentName(), request.documentType(), docSize, request.isAuthentication()
|
||||||
|
);
|
||||||
|
dialog.showAndAwait().whenComplete((approved, err) -> {
|
||||||
|
if (err != null) {
|
||||||
|
future.complete(false);
|
||||||
|
} else {
|
||||||
|
future.complete(Boolean.TRUE.equals(approved));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Erro ao exibir diálogo de confirmação de assinatura: {}", ex.getMessage());
|
||||||
|
future.complete(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user