diff --git a/.gitignore b/.gitignore index 96b1dbb06fdba35f1ea7c4d240a7d4fa14f4129b..e7d3759aec8fb125bae12729434f8c08268ecf6f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /src/main/webapp/WEB-INF/classes/ /src/main/webapp/WEB-INF/lib/ /src/main/webapp/WEB-INF/site/ +/src/main/webapp/WEB-INF/plugin/ /src/main/webapp/WEB-INF/env/crawler/lib/ /src/main/webapp/WEB-INF/env/suggest/lib/ /src/main/webapp/WEB-INF/env/thumbnail/lib/ diff --git a/deps.xml b/deps.xml index 6721beb9b2fbf2c5c0915024fa0c3a812634469b..f7be6bea6a824091cc59341e1b380f6f39d08d5f 100644 --- a/deps.xml +++ b/deps.xml @@ -16,6 +16,8 @@ <mkdir dir="${target.dir}" /> <delete dir="${webinf.dir}/lib" /> <mkdir dir="${webinf.dir}/lib" /> + <delete dir="${webinf.dir}/plugin" /> + <mkdir dir="${webinf.dir}/plugin" /> <delete dir="${crawler.dir}/lib" /> <mkdir dir="${crawler.dir}/lib" /> <delete dir="${suggest.dir}/lib" /> @@ -42,7 +44,7 @@ <cutdirsmapper dirs="2" /> </unzip> <!-- fess-ds-atlassian --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-atlassian" /> @@ -50,7 +52,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-box --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-box" /> @@ -58,7 +60,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-csv --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-csv" /> @@ -66,7 +68,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-db --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-db" /> @@ -74,7 +76,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-dropbox --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-dropbox" /> @@ -82,7 +84,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-elasticsearch --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-elasticsearch" /> @@ -90,7 +92,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-gitbucket --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-gitbucket" /> @@ -98,7 +100,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-gsuite --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-gsuite" /> @@ -106,7 +108,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-json --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-json" /> @@ -114,7 +116,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-office365 --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-office365" /> @@ -122,7 +124,7 @@ <param name="file.version" value="13.2.0" /> </antcall> <!-- fess-ds-slack --> - <antcall target="install.lib.jar"> + <antcall target="install.plugin.jar"> <param name="repo.url" value="${maven.release.repo.url}" /> <param name="jar.groupId" value="org/codelibs/fess" /> <param name="jar.artifactId" value="fess-ds-slack" /> @@ -143,11 +145,11 @@ todir="${thumbnail.dir}/lib"/> </target> - <target name="install.lib.jar"> + <target name="install.plugin.jar"> <get dest="${target.dir}"> <url url="${repo.url}/${jar.groupId}/${jar.artifactId}/${jar.version}/${jar.artifactId}-${file.version}.jar" /> </get> <copy file="${target.dir}/${jar.artifactId}-${file.version}.jar" - todir="${webinf.dir}/lib"/> + todir="${webinf.dir}/plugin"/> </target> </project> diff --git a/src/main/java/org/codelibs/fess/app/web/admin/dataconfig/AdminDataconfigAction.java b/src/main/java/org/codelibs/fess/app/web/admin/dataconfig/AdminDataconfigAction.java index 032e772defeb68e0064e2451d9d5860068ace631..f0fe61f9e04e38eef4f957e9a2032be878ffec53 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/dataconfig/AdminDataconfigAction.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/dataconfig/AdminDataconfigAction.java @@ -323,9 +323,9 @@ public class AdminDataconfigAction extends FessAdminAction { } protected void registerHandlerNames(final RenderData data) { - final List<String> dataStoreNameList = dataStoreFactory.getDataStoreNameList(); + final String[] dataStoreNames = dataStoreFactory.getDataStoreNames(); final List<Map<String, String>> itemList = new ArrayList<>(); - for (final String name : dataStoreNameList) { + for (final String name : dataStoreNames) { final Map<String, String> map = new HashMap<>(); map.put(Constants.ITEM_LABEL, name); map.put(Constants.ITEM_VALUE, name); diff --git a/src/main/java/org/codelibs/fess/ds/DataStoreFactory.java b/src/main/java/org/codelibs/fess/ds/DataStoreFactory.java index e845557651c946db5b327631e722b5cc8f3513a7..bb862908b70d2d11cc771d400fb2e1e0c1f1dda7 100644 --- a/src/main/java/org/codelibs/fess/ds/DataStoreFactory.java +++ b/src/main/java/org/codelibs/fess/ds/DataStoreFactory.java @@ -15,21 +15,43 @@ */ package org.codelibs.fess.ds; -import java.util.ArrayList; -import java.util.Collections; +import java.io.File; +import java.io.InputStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.helper.PluginHelper; +import org.codelibs.fess.util.ResourceUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; public class DataStoreFactory { private static final Logger logger = LoggerFactory.getLogger(DataStoreFactory.class); protected Map<String, DataStore> dataStoreMap = new LinkedHashMap<>(); + protected String[] dataStoreNames = StringUtil.EMPTY_STRINGS; + + protected long lastLoadedTime = 0; + public void add(final String name, final DataStore dataStore) { if (name == null || dataStore == null) { throw new IllegalArgumentException("name or dataStore is null."); @@ -37,19 +59,58 @@ public class DataStoreFactory { if (logger.isDebugEnabled()) { logger.debug("Loaded " + name); } - dataStoreMap.put(name, dataStore); + dataStoreMap.put(name.toLowerCase(Locale.ROOT), dataStore); + dataStoreMap.put(dataStore.getClass().getSimpleName().toLowerCase(Locale.ROOT), dataStore); } public DataStore getDataStore(final String name) { - return dataStoreMap.get(name); + if (name == null) { + return null; + } + return dataStoreMap.get(name.toLowerCase(Locale.ROOT)); } - public List<String> getDataStoreNameList() { - final Set<String> nameSet = dataStoreMap.keySet(); - final List<String> nameList = new ArrayList<>(); - nameList.addAll(nameSet); - Collections.sort(nameList); - return nameList; + public String[] getDataStoreNames() { + if (System.currentTimeMillis() - lastLoadedTime > 60000L) { + final List<String> nameList = loadDataStoreNameList(); + dataStoreNames = nameList.toArray(n -> new String[nameList.size()]); + } + return dataStoreNames; + } + + protected List<String> loadDataStoreNameList() { + final Set<String> nameSet = new HashSet<>(); + final File[] jarFiles = ResourceUtil.getPluginJarFiles(PluginHelper.ArtifactType.DATA_STORE.getId()); + for (final File jarFile : jarFiles) { + try (FileSystem fs = FileSystems.newFileSystem(jarFile.toPath(), ClassLoader.getSystemClassLoader())) { + final Path xmlPath = fs.getPath("fess_ds++.xml"); + try (InputStream is = Files.newInputStream(xmlPath)) { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + final DocumentBuilder builder = factory.newDocumentBuilder(); + + final Document doc = builder.parse(is); + final NodeList nodeList = doc.getElementsByTagName("component"); + for (int i = 0; i < nodeList.getLength(); i++) { + final Node node = nodeList.item(i); + final NamedNodeMap attributes = node.getAttributes(); + if (attributes != null) { + final Node classAttr = attributes.getNamedItem("class"); + if (classAttr != null) { + final String value = classAttr.getNodeValue(); + if (StringUtil.isNotBlank(value)) { + final String[] values = value.split("\\."); + nameSet.add(values[values.length - 1]); + } + } + } + } + } + } catch (final Exception e) { + logger.warn("Failed to load " + jarFile.getAbsolutePath(), e); + } + } + return nameSet.stream().sorted().collect(Collectors.toList()); } } diff --git a/src/main/java/org/codelibs/fess/exception/PluginException.java b/src/main/java/org/codelibs/fess/exception/PluginException.java new file mode 100644 index 0000000000000000000000000000000000000000..41f7961919f0bd0cd028d9da2571b8c749cb3d7a --- /dev/null +++ b/src/main/java/org/codelibs/fess/exception/PluginException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2019 CodeLibs Project and the Others. + * + * 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 org.codelibs.fess.exception; + +public class PluginException extends FessSystemException { + + private static final long serialVersionUID = 1L; + + public PluginException(String message, Throwable cause) { + super(message, cause); + } + + public PluginException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/codelibs/fess/helper/PluginHelper.java b/src/main/java/org/codelibs/fess/helper/PluginHelper.java index e5c29906354f81f5bf94d538bbb9642e3ae08b6e..c9935eda67ea9f430d47fdaf56fb7dcf0ca811a5 100644 --- a/src/main/java/org/codelibs/fess/helper/PluginHelper.java +++ b/src/main/java/org/codelibs/fess/helper/PluginHelper.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -28,9 +29,12 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.servlet.ServletContext; + import org.codelibs.core.io.CopyUtil; import org.codelibs.curl.Curl; import org.codelibs.curl.CurlResponse; +import org.codelibs.fess.exception.PluginException; import org.codelibs.fess.util.ComponentUtil; import org.codelibs.fess.util.ResourceUtil; import org.codelibs.nekohtml.parsers.DOMParser; @@ -41,8 +45,6 @@ import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; -import javax.servlet.ServletContext; - // TODO: refactoring, exception handling, improving codes public class PluginHelper { private static final Logger logger = LoggerFactory.getLogger(PluginHelper.class); @@ -64,12 +66,12 @@ public class PluginHelper { protected List<Artifact> processRepository(final ArtifactType artifactType, final String url) { final List<Artifact> list = new ArrayList<>(); - final String repoContent = getRepositoryContentAsString(url); + final String repoContent = getRepositoryContent(url); final Matcher matcher = Pattern.compile("href=\"[^\"]*(" + artifactType.getId() + "[a-zA-Z0-9\\-]+)/?\"").matcher(repoContent); while (matcher.find()) { final String name = matcher.group(1); final String pluginUrl = url + (url.endsWith("/") ? name + "/" : "/" + name + "/"); - final String mavenMetadata = getRepositoryContentAsString(pluginUrl + "maven-metadata.xml"); + final String mavenMetadata = getRepositoryContent(pluginUrl + "maven-metadata.xml"); try (final StringReader reader = new StringReader(mavenMetadata)) { final DOMParser parser = new DOMParser(); parser.parse(new InputSource(reader)); @@ -86,7 +88,7 @@ public class PluginHelper { return list; } - protected String getRepositoryContentAsString(final String url) { + protected String getRepositoryContent(final String url) { try (final CurlResponse response = Curl.get(url).execute()) { return response.getContentAsString(); } catch (final IOException e) { @@ -94,16 +96,8 @@ public class PluginHelper { } } - protected InputStream getRepositoryContentAsStream(final String url) { - try (final CurlResponse response = Curl.get(url).execute()) { - return response.getContentAsStream(); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - public Artifact[] getInstalledArtifacts(final ArtifactType artifactType) { - final File[] jarFiles = ResourceUtil.getJarFiles(artifactType.getId()); + final File[] jarFiles = ResourceUtil.getPluginJarFiles(artifactType.getId()); final List<Artifact> list = new ArrayList<>(jarFiles.length); for (final File file : jarFiles) { list.add(getArtifactFromFileName(artifactType, file.getName())); @@ -119,39 +113,32 @@ public class PluginHelper { return new Artifact(artifactName, artifactVersion, null); } - public boolean installArtifact(Artifact artifact) { - try (final InputStream in = getRepositoryContentAsStream(artifact.getUrl())) { - final String fileName = artifact.getName() + "-" + artifact.getVersion() + ".jar"; - final File file = Paths.get(getLibPath().toString(), fileName).toFile(); - if (!file.exists()) { - file.createNewFile(); - CopyUtil.copy(in, file); - } else { - final File tempFile = File.createTempFile(fileName, "bk"); - CopyUtil.copy(file, tempFile); - CopyUtil.copy(in, file); - tempFile.delete(); + public void installArtifact(Artifact artifact) { + final String fileName = artifact.getFileName(); + try (final CurlResponse response = Curl.get(artifact.getUrl()).execute()) { + try (final InputStream in = response.getContentAsStream()) { + CopyUtil.copy(in, ResourceUtil.getPluginPath(fileName).toFile()); } - return file.exists(); } catch (final Exception e) { - logger.warn("Failed to install the artifact " + artifact.getName(), e); - return false; + throw new PluginException("Failed to install the artifact " + artifact.getName(), e); } } - public boolean deleteInstalledArtifact(Artifact artifact) { - final String fileName = artifact.getName() + "-" + artifact.getVersion() + ".jar"; - final File file = Paths.get(getLibPath().toString(), fileName).toFile(); - if (file != null && file.exists() && file.delete()) { - return true; - } else { - logger.warn("Failed to delete the artifact " + file.getAbsolutePath()); - return false; + public void deleteInstalledArtifact(Artifact artifact) { + final String fileName = artifact.getFileName(); + final Path jarPath = Paths.get(getPluginPath().toString(), fileName); + if (!Files.exists(jarPath)) { + throw new PluginException(fileName + " does not exist."); + } + try { + Files.delete(jarPath); + } catch (IOException e) { + throw new PluginException("Failed to delete the artifact " + fileName, e); } } - protected Path getLibPath() { - return Paths.get(ComponentUtil.getComponent(ServletContext.class).getRealPath("/WEB-INF/lib/")); + protected Path getPluginPath() { + return Paths.get(ComponentUtil.getComponent(ServletContext.class).getRealPath("/WEB-INF/plugin")); } public static class Artifact { @@ -173,6 +160,10 @@ public class PluginHelper { return version; } + public String getFileName() { + return name + "-" + version + ".jar"; + } + public String getUrl() { return url; } diff --git a/src/main/java/org/codelibs/fess/job/CrawlJob.java b/src/main/java/org/codelibs/fess/job/CrawlJob.java index b0489cdac440c7d4078c016cbccf5fcebf7062ea..db47f48d4e47f0f54636f2e248538ce3601a3534 100644 --- a/src/main/java/org/codelibs/fess/job/CrawlJob.java +++ b/src/main/java/org/codelibs/fess/job/CrawlJob.java @@ -237,6 +237,9 @@ public class CrawlJob extends ExecJob { // WEB-INF/env/crawler/lib appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/env/" + getExecuteType() + "/lib")), "WEB-INF" + File.separator + "env" + File.separator + getExecuteType() + File.separator + "lib" + File.separator); + // WEB-INF/plugin + appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/plugin")), "WEB-INF" + File.separator + "plugin" + + File.separator); final File targetLibDir = new File(targetDir, "fess" + File.separator + "WEB-INF" + File.separator + "lib"); if (targetLibDir.isDirectory()) { appendJarFile(cpSeparator, buf, targetLibDir, targetLibDir.getAbsolutePath() + File.separator); diff --git a/src/main/java/org/codelibs/fess/job/GenerateThumbnailJob.java b/src/main/java/org/codelibs/fess/job/GenerateThumbnailJob.java index 280ae7b31f527c5a42011fc8cf31a01a3dc45c40..fc6c931ca6f129145b1a74c795bfe5714eb4e219 100644 --- a/src/main/java/org/codelibs/fess/job/GenerateThumbnailJob.java +++ b/src/main/java/org/codelibs/fess/job/GenerateThumbnailJob.java @@ -134,6 +134,9 @@ public class GenerateThumbnailJob extends ExecJob { // WEB-INF/env/thumbnail/lib appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/env/" + getExecuteType() + "/lib")), "WEB-INF" + File.separator + "env" + File.separator + getExecuteType() + File.separator + "lib" + File.separator); + // WEB-INF/plugin + appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/plugin")), "WEB-INF" + File.separator + "plugin" + + File.separator); final File targetLibDir = new File(targetDir, "fess" + File.separator + "WEB-INF" + File.separator + "lib"); if (targetLibDir.isDirectory()) { appendJarFile(cpSeparator, buf, targetLibDir, targetLibDir.getAbsolutePath() + File.separator); diff --git a/src/main/java/org/codelibs/fess/job/SuggestJob.java b/src/main/java/org/codelibs/fess/job/SuggestJob.java index 4bb3ab4f222ca201c47493e987ff1b3790a805f3..7af5101cadeeff8f6448b706303642e75fd95e06 100644 --- a/src/main/java/org/codelibs/fess/job/SuggestJob.java +++ b/src/main/java/org/codelibs/fess/job/SuggestJob.java @@ -121,6 +121,9 @@ public class SuggestJob extends ExecJob { // WEB-INF/env/suggest/lib appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/env/" + getExecuteType() + "/lib")), "WEB-INF" + File.separator + "env" + File.separator + getExecuteType() + File.separator + "lib" + File.separator); + // WEB-INF/plugin + appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/plugin")), "WEB-INF" + File.separator + "plugin" + + File.separator); final File targetLibDir = new File(targetDir, "fess" + File.separator + "WEB-INF" + File.separator + "lib"); if (targetLibDir.isDirectory()) { appendJarFile(cpSeparator, buf, targetLibDir, targetLibDir.getAbsolutePath() + File.separator); diff --git a/src/main/java/org/codelibs/fess/util/ResourceUtil.java b/src/main/java/org/codelibs/fess/util/ResourceUtil.java index 2658eb0a89a55d79a105bf62f06161ebb2a312f8..5a089f0a69c30314da63b712a019b24ba22b4c48 100644 --- a/src/main/java/org/codelibs/fess/util/ResourceUtil.java +++ b/src/main/java/org/codelibs/fess/util/ResourceUtil.java @@ -111,6 +111,10 @@ public class ResourceUtil { return getPath("site", names); } + public static Path getPluginPath(final String... names) { + return getPath("plutin", names); + } + public static Path getProjectPropertiesFile() { return getPath("", "project.properties"); } @@ -156,6 +160,19 @@ public class ResourceUtil { return libDir.listFiles((FilenameFilter) (file, name) -> name.startsWith(namePrefix)); } + public static File[] getPluginJarFiles(final String namePrefix) { + final ServletContext context = LaServletContextUtil.getServletContext(); + if (context == null) { + return new File[0]; + } + final String libPath = context.getRealPath("/WEB-INF/plugin"); + if (StringUtil.isBlank(libPath)) { + return new File[0]; + } + final File libDir = new File(libPath); + return libDir.listFiles((FilenameFilter) (file, name) -> name.startsWith(namePrefix)); + } + public static String resolve(final String value) { if (value == null) { return null;