<commit_before>package org.ligoj.app.resource.plugin;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.ligoj.app.api.FeaturePlugin;
import org.ligoj.app.api.ServicePlugin;
import org.ligoj.app.api.SubscriptionMode;
import org.ligoj.app.api.ToolPlugin;
import org.ligoj.app.dao.NodeRepository;
import org.ligoj.app.dao.PluginRepository;
import org.ligoj.app.dao.SubscriptionRepository;
import org.ligoj.app.model.Node;
import org.ligoj.app.model.Plugin;
import org.ligoj.app.model.PluginType;
import org.ligoj.app.resource.node.NodeResource;
import org.ligoj.bootstrap.core.NamedBean;
import org.ligoj.bootstrap.core.SpringUtils;
import org.ligoj.bootstrap.core.dao.csv.CsvForJpa;
import org.ligoj.bootstrap.core.resource.BusinessException;
import org.ligoj.bootstrap.core.resource.TechnicalException;
import org.ligoj.bootstrap.resource.system.configuration.ConfigurationResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.Persistable;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * Manage plug-in life-cycle.
 */
@Path("/plugin")
@Slf4j
@Component
@Transactional
@Produces(MediaType.APPLICATION_JSON)
public class PluginResource {

	private static final String DEFAULT_PLUGIN_URL = "http://central.maven.org/maven2/org/ligoj/plugin/";

	private static final String DEFAULT_PLUGIN_SEARCH_QUERY = "http://search.maven.org/solrsearch/select?wt=json&q=org.ligoj.plugin+AND+a:{0}*";

	@Autowired
	private NodeRepository nodeRepository;

	@Autowired
	private PluginRepository repository;

	@Autowired
	private SubscriptionRepository subscriptionRepository;

	@Autowired
	protected CsvForJpa csvForJpa;

	@Autowired
	private ConfigurationResource configuration;

	@Autowired
	protected EntityManager em;

	/**
	 * Return the plug-ins download URL.
	 * 
	 * @return The plug-ins download URL. Ends with "/".
	 */
	private String getPluginUrl() {
		return StringUtils.appendIfMissing(ObjectUtils.defaultIfNull(configuration.get("plugins.url"), DEFAULT_PLUGIN_URL), "/");
	}

	/**
	 * Return the plug-ins search URL.
	 * 
	 * @return The plug-ins search URL. 
	 */
	private String getPluginSearchUrl() {
		return ObjectUtils.defaultIfNull(configuration.get("plugins.search.url"), DEFAULT_PLUGIN_SEARCH_QUERY);
	}

	/**
	 * Return all plug-ins with details.
	 *
	 * @return All plug-ins with details.
	 */
	@GET
	public List<PluginVo> findAll() {
		// Get the existing plug-in features
		return repository.findAll().stream().map(p -> {
			final String key = p.getKey();
			final PluginVo vo = new PluginVo();
			vo.setId(key);

			// Find the associated feature class
			final FeaturePlugin feature = SpringUtils.getApplicationContext().getBeansOfType(FeaturePlugin.class).values().stream()
					.filter(f -> key.equals(f.getKey())).findFirst().get();
			vo.setName(StringUtils.removeStart(feature.getName(), "Ligoj - Plugin "));
			vo.setLocation(getPluginLocation(feature).getPath());
			vo.setVendor(feature.getVendor());
			vo.setPlugin(p);
			if (p.getType() != PluginType.FEATURE) {
				// This is a node (service or tool) add statistics and details
				vo.setNodes(nodeRepository.countByRefined(key));
				vo.setSubscriptions(subscriptionRepository.countByNode(key));
				vo.setNode(NodeResource.toVo(nodeRepository.findOne(key)));
			}
			return vo;
		}).sorted(Comparator.comparing(NamedBean::getId)).collect(Collectors.toList());
	}

	/**
	 * Search plug-ins in repo which can be installed.
	 *
	 * @return All plug-ins artifacts name.
	 */
	@GET
	@Path("search")
	public List<String> search(@Context final UriInfo uriInfo) {
		final String queryUrl = MessageFormat.format(getPluginSearchUrl(), uriInfo.getQueryParameters().getFirst("q"));
		final String searchResult = new CurlProcessor().get(queryUrl);

		// extract artifacts
		final Matcher matcher = Pattern.compile("\"a\":\"(.*?)\"").matcher(searchResult);
		final List<String> result = new ArrayList<>();
		while (matcher.find()) {
			result.add(matcher.group(1));
		}
		return result;
	}

	/**
	 * Install the last available version of given plug-in from the remote server.
	 * 
	 * @param artifact
	 *            The Maven artifact identifier and also corresponding to the plug-in simple name.
	 */
	@POST
	@Path("{artifact:[\\w-]+}")
	public void install(@PathParam("artifact") final String artifact) {
		final String metaData = StringUtils.defaultString(new CurlProcessor().get(getPluginUrl() + artifact + "/maven-metadata.xml"), "");
		final Matcher matcher = Pattern.compile("<latest>([^<]+)</latest>").matcher(metaData);
		if (!matcher.find()) {
			// Plug-in not found
			throw new BusinessException(
					String.format("Versions discovery cannot be performed from from remote server %s for plugin %s", getPluginUrl(), artifact));
		}
		install(artifact, matcher.group(1));
	}

	/**
	 * Remove all versions the specified plug-in.
	 * 
	 * @param artifact
	 *            The Maven artifact identifier and also corresponding to the plug-in simple name.
	 */
	@DELETE
	@Path("{artifact:[\\w-]+}")
	public void remove(@PathParam("artifact") final String artifact) throws IOException {
		Files.list(getPluginClassLoader().getPluginDirectory()).filter(p -> p.getFileName().toString().matches("^" + artifact + "(-.*)?\\.jar$"))
				.forEach(p -> p.toFile().delete());
		log.info("Plugin {} has been deleted, restart is required", artifact);
	}

	/**<commit_msg>Typo in error message<commit_after>package org.ligoj.app.resource.plugin;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.ligoj.app.api.FeaturePlugin;
import org.ligoj.app.api.ServicePlugin;
import org.ligoj.app.api.SubscriptionMode;
import org.ligoj.app.api.ToolPlugin;
import org.ligoj.app.dao.NodeRepository;
import org.ligoj.app.dao.PluginRepository;
import org.ligoj.app.dao.SubscriptionRepository;
import org.ligoj.app.model.Node;
import org.ligoj.app.model.Plugin;
import org.ligoj.app.model.PluginType;
import org.ligoj.app.resource.node.NodeResource;
import org.ligoj.bootstrap.core.NamedBean;
import org.ligoj.bootstrap.core.SpringUtils;
import org.ligoj.bootstrap.core.dao.csv.CsvForJpa;
import org.ligoj.bootstrap.core.resource.BusinessException;
import org.ligoj.bootstrap.core.resource.TechnicalException;
import org.ligoj.bootstrap.resource.system.configuration.ConfigurationResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.Persistable;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * Manage plug-in life-cycle.
 */
@Path("/plugin")
@Slf4j
@Component
@Transactional
@Produces(MediaType.APPLICATION_JSON)
public class PluginResource {

	private static final String DEFAULT_PLUGIN_URL = "http://central.maven.org/maven2/org/ligoj/plugin/";

	private static final String DEFAULT_PLUGIN_SEARCH_QUERY = "http://search.maven.org/solrsearch/select?wt=json&q=org.ligoj.plugin+AND+a:{0}*";

	@Autowired
	private NodeRepository nodeRepository;

	@Autowired
	private PluginRepository repository;

	@Autowired
	private SubscriptionRepository subscriptionRepository;

	@Autowired
	protected CsvForJpa csvForJpa;

	@Autowired
	private ConfigurationResource configuration;

	@Autowired
	protected EntityManager em;

	/**
	 * Return the plug-ins download URL.
	 * 
	 * @return The plug-ins download URL. Ends with "/".
	 */
	private String getPluginUrl() {
		return StringUtils.appendIfMissing(ObjectUtils.defaultIfNull(configuration.get("plugins.url"), DEFAULT_PLUGIN_URL), "/");
	}

	/**
	 * Return the plug-ins search URL.
	 * 
	 * @return The plug-ins search URL. 
	 */
	private String getPluginSearchUrl() {
		return ObjectUtils.defaultIfNull(configuration.get("plugins.search.url"), DEFAULT_PLUGIN_SEARCH_QUERY);
	}

	/**
	 * Return all plug-ins with details.
	 *
	 * @return All plug-ins with details.
	 */
	@GET
	public List<PluginVo> findAll() {
		// Get the existing plug-in features
		return repository.findAll().stream().map(p -> {
			final String key = p.getKey();
			final PluginVo vo = new PluginVo();
			vo.setId(key);

			// Find the associated feature class
			final FeaturePlugin feature = SpringUtils.getApplicationContext().getBeansOfType(FeaturePlugin.class).values().stream()
					.filter(f -> key.equals(f.getKey())).findFirst().get();
			vo.setName(StringUtils.removeStart(feature.getName(), "Ligoj - Plugin "));
			vo.setLocation(getPluginLocation(feature).getPath());
			vo.setVendor(feature.getVendor());
			vo.setPlugin(p);
			if (p.getType() != PluginType.FEATURE) {
				// This is a node (service or tool) add statistics and details
				vo.setNodes(nodeRepository.countByRefined(key));
				vo.setSubscriptions(subscriptionRepository.countByNode(key));
				vo.setNode(NodeResource.toVo(nodeRepository.findOne(key)));
			}
			return vo;
		}).sorted(Comparator.comparing(NamedBean::getId)).collect(Collectors.toList());
	}

	/**
	 * Search plug-ins in repo which can be installed.
	 *
	 * @return All plug-ins artifacts name.
	 */
	@GET
	@Path("search")
	public List<String> search(@Context final UriInfo uriInfo) {
		final String queryUrl = MessageFormat.format(getPluginSearchUrl(), uriInfo.getQueryParameters().getFirst("q"));
		final String searchResult = new CurlProcessor().get(queryUrl);

		// extract artifacts
		final Matcher matcher = Pattern.compile("\"a\":\"(.*?)\"").matcher(searchResult);
		final List<String> result = new ArrayList<>();
		while (matcher.find()) {
			result.add(matcher.group(1));
		}
		return result;
	}

	/**
	 * Install the last available version of given plug-in from the remote server.
	 * 
	 * @param artifact
	 *            The Maven artifact identifier and also corresponding to the plug-in simple name.
	 */
	@POST
	@Path("{artifact:[\\w-]+}")
	public void install(@PathParam("artifact") final String artifact) {
		final String metaData = StringUtils.defaultString(new CurlProcessor().get(getPluginUrl() + artifact + "/maven-metadata.xml"), "");
		final Matcher matcher = Pattern.compile("<latest>([^<]+)</latest>").matcher(metaData);
		if (!matcher.find()) {
			// Plug-in not found
			throw new BusinessException(
					String.format("Versions discovery cannot be performed from the remote server %s for plugin %s", getPluginUrl(), artifact));
		}
		install(artifact, matcher.group(1));
	}

	/**
	 * Remove all versions the specified plug-in.
	 * 
	 * @param artifact
	 *            The Maven artifact identifier and also corresponding to the plug-in simple name.
	 */
	@DELETE
	@Path("{artifact:[\\w-]+}")
	public void remove(@PathParam("artifact") final String artifact) throws IOException {
		Files.list(getPluginClassLoader().getPluginDirectory()).filter(p -> p.getFileName().toString().matches("^" + artifact + "(-.*)?\\.jar$"))
				.forEach(p -> p.toFile().delete());
		log.info("Plugin {} has been deleted, restart is required", artifact);
	}

	/**
{'fraction_non_alphanumeric': 0.13363195222097798, 'fraction_numerical': 0.0013437849944008958, 'mean_word_length': 4.794117647058823, 'pattern_counts': {'":': 2, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4444079', 'n_tokens_mistral': 4357, 'n_tokens_neox': 4110, 'n_words': 873}