use std::collections::HashMap;
use std::path::PathBuf;
use anyhow::{Context, Result};
use std::sync::{Arc, Mutex};
use crate::plugin_api::{Plugin, PluginDescriptor};
use crate::artifact::{Artifact, LocalRepository};
use crate::resolver::{resolver::DependencyResolver, repository::RemoteRepository, downloader::ArtifactDownloader};
use crate::model::Dependency;
use crate::model::parser::parse_pom;
pub struct PluginRegistry {
plugins: Arc<Mutex<HashMap<String, Arc<dyn Plugin>>>>,
local_repository: Box<dyn LocalRepository>,
resolver: DependencyResolver,
downloader: ArtifactDownloader,
remote_repositories: Vec<RemoteRepository>,
}
impl PluginRegistry {
pub fn new(local_repository: Box<dyn LocalRepository>, resolver: DependencyResolver) -> Self {
let remote_repos = resolver.remote_repositories().to_vec();
Self {
plugins: Arc::new(Mutex::new(HashMap::new())),
local_repository,
resolver,
downloader: ArtifactDownloader::new(),
remote_repositories: remote_repos,
}
}
pub fn with_remote_repositories(mut self, repositories: Vec<RemoteRepository>) -> Self {
self.remote_repositories = repositories;
self
}
pub fn get_plugin(
&self,
group_id: &str,
artifact_id: &str,
version: &str,
) -> Result<Option<Arc<dyn Plugin>>> {
let key = format!("{group_id}:{artifact_id}:{version}");
{
let plugins = self.plugins.lock().unwrap();
if let Some(plugin) = plugins.get(&key) {
return Ok(Some(plugin.clone()));
}
}
let plugin = self.load_plugin(group_id, artifact_id, version)?;
if let Some(ref plugin) = plugin {
let mut plugins = self.plugins.lock().unwrap();
plugins.insert(key, plugin.clone());
}
Ok(plugin)
}
fn load_plugin(
&self,
group_id: &str,
artifact_id: &str,
version: &str,
) -> Result<Option<Arc<dyn Plugin>>> {
let dependency = Dependency {
group_id: group_id.to_string(),
artifact_id: artifact_id.to_string(),
version: Some(version.to_string()),
type_: None,
classifier: None,
scope: Some("compile".to_string()),
optional: Some(false),
exclusions: None,
};
let artifact = self.resolver.resolve_dependency(&dependency)
.context("Failed to resolve plugin artifact")?;
let artifact = artifact.ok_or_else(|| {
anyhow::anyhow!("Plugin {group_id}:{artifact_id}:{version} not found")
})?;
let jar_path = artifact.file.ok_or_else(|| {
anyhow::anyhow!("Plugin artifact has no file path")
})?;
let mut descriptor = PluginDescriptor::from_jar(&jar_path)
.with_context(|| format!("Failed to parse plugin descriptor from {jar_path:?}"))?;
let plugin_dependencies = self.resolve_plugin_dependencies(group_id, artifact_id, version)?;
descriptor.dependencies = plugin_dependencies;
let dependency_artifacts = self.resolver.resolve_dependencies(&descriptor.dependencies)
.context("Failed to resolve plugin dependencies")?;
let plugin: Arc<dyn Plugin> = Arc::new(LoadedPlugin {
descriptor,
jar_path,
dependency_artifacts,
});
Ok(Some(plugin))
}
fn resolve_plugin_dependencies(
&self,
group_id: &str,
artifact_id: &str,
version: &str,
) -> Result<Vec<Dependency>> {
let plugin_artifact = Artifact::new(group_id, artifact_id, version);
let mut pom_artifact = plugin_artifact.clone();
pom_artifact.coordinates.packaging = Some("pom".to_string());
let pom_path = self.local_repository.artifact_path(&pom_artifact);
if !pom_path.exists() {
let pom_path_parent = pom_path.parent()
.ok_or_else(|| anyhow::anyhow!("Invalid POM path"))?;
std::fs::create_dir_all(pom_path_parent)
.context("Failed to create POM directory")?;
self.downloader.download_pom(&plugin_artifact, &self.remote_repositories[0], &pom_path)
.context("Failed to download plugin POM")?;
}
let model = parse_pom(&std::fs::read_to_string(&pom_path)
.context("Failed to read plugin POM")?)
.map_err(|e| anyhow::anyhow!("Failed to parse plugin POM: {e}"))?;
let mut dependencies = Vec::new();
if let Some(ref build) = model.build {
if let Some(ref plugins) = build.plugins {
for plugin in plugins {
if plugin.artifact_id == artifact_id {
if let Some(ref deps) = plugin.dependencies {
dependencies.extend(deps.clone());
}
}
}
}
}
dependencies.extend(model.dependencies_vec());
Ok(dependencies)
}
}
struct LoadedPlugin {
descriptor: PluginDescriptor,
jar_path: PathBuf,
dependency_artifacts: Vec<Artifact>,
}
impl LoadedPlugin {
pub fn classpath(&self) -> String {
use crate::compiler::ClasspathBuilder;
let mut builder = ClasspathBuilder::new();
builder = builder.add_jar(self.jar_path.clone());
for dep_artifact in &self.dependency_artifacts {
if let Some(ref file) = dep_artifact.file {
builder = builder.add_jar(file.clone());
}
}
builder.build()
}
}
impl Plugin for LoadedPlugin {
fn descriptor(&self) -> &PluginDescriptor {
&self.descriptor
}
fn get_mojo(&self, goal: &str) -> Option<Box<dyn crate::plugin_api::Mojo>> {
let goal_info = self.descriptor.goals.iter()
.find(|g| g.name == goal)?;
Some(Box::new(JavaMojo {
plugin_jar: self.jar_path.clone(),
classpath: self.classpath(),
goal: goal.to_string(),
phase: goal_info.phase.clone(),
description: goal_info.description.clone(),
plugin_group_id: self.descriptor.group_id.clone(),
plugin_artifact_id: self.descriptor.artifact_id.clone(),
plugin_version: self.descriptor.version.clone(),
}))
}
}
#[allow(dead_code)]
struct JavaMojo {
plugin_jar: PathBuf,
classpath: String,
goal: String,
phase: Option<String>,
description: Option<String>,
plugin_group_id: String,
plugin_artifact_id: String,
plugin_version: String,
}
impl crate::plugin_api::Mojo for JavaMojo {
fn execute(&self) -> Result<(), Box<dyn std::error::Error>> {
tracing::info!(
"Executing Java mojo {}:{}:{}:{} (phase: {:?})",
self.plugin_group_id,
self.plugin_artifact_id,
self.plugin_version,
self.goal,
self.phase
);
#[cfg(feature = "jni")]
{
if let Ok(jni_executor) = crate::plugin_api::jni_executor::JniMojoExecutor::new(
self.plugin_jar.clone(),
self.classpath.clone(),
) {
if let Err(e) = jni_executor.execute_mojo("org.apache.maven.plugin.Mojo", &serde_json::json!({})) {
tracing::warn!("JNI execution failed, falling back to external process: {}", e);
} else {
return Ok(());
}
}
}
use crate::plugin_api::external_executor::ExternalMavenExecutor;
let executor = ExternalMavenExecutor;
let project_dir = std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."));
executor.execute_plugin_goal(
&self.plugin_group_id,
&self.plugin_artifact_id,
&self.plugin_version,
&self.goal,
&project_dir,
)?;
Ok(())
}
}
#[allow(dead_code)]
impl JavaMojo {
fn find_java() -> Option<PathBuf> {
if let Ok(java_home) = std::env::var("JAVA_HOME") {
let java_path = PathBuf::from(java_home)
.join("bin")
.join(if cfg!(windows) { "java.exe" } else { "java" });
if java_path.exists() {
return Some(java_path);
}
}
which::which("java").ok()
}
}
impl Default for PluginRegistry {
fn default() -> Self {
use crate::artifact::repository::DefaultLocalRepository;
let local_repo = Box::new(DefaultLocalRepository::default());
let local_repo_for_resolver: Box<dyn LocalRepository> = Box::new(DefaultLocalRepository::default());
let resolver = DependencyResolver::new(local_repo_for_resolver);
Self::new(local_repo, resolver)
}
}