package org.fastcatsearch.plugin; import org.fastcatsearch.alert.ClusterAlertService; import org.fastcatsearch.control.JobService; import org.fastcatsearch.env.Environment; import org.fastcatsearch.exception.FastcatSearchException; import org.fastcatsearch.http.HttpRequestService; import org.fastcatsearch.ir.AnalyzerProvider; import org.fastcatsearch.ir.analysis.AnalyzerFactoryManager; import org.fastcatsearch.ir.util.Formatter; import org.fastcatsearch.job.Job; import org.fastcatsearch.plugin.PluginSetting.Action; import org.fastcatsearch.plugin.PluginSetting.PluginSchedule; import org.fastcatsearch.plugin.analysis.AnalysisPlugin; import org.fastcatsearch.plugin.analysis.AnalysisPluginSetting; import org.fastcatsearch.plugin.analysis.AnalyzerInfo; import org.fastcatsearch.service.AbstractService; import org.fastcatsearch.service.ServiceManager; import org.fastcatsearch.settings.SettingFileNames; import org.fastcatsearch.settings.Settings; import org.fastcatsearch.util.DynamicClassLoader; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.*; import java.text.ParseException; import java.util.*; import java.util.Map.Entry; public class PluginService extends AbstractService implements AnalyzerProvider { private static final String pluginActionPrefix = "/_plugin/"; private Map<String, Plugin> pluginMap; private PluginAnalyzerFactoryManager pluginAnalyzerFactoryManager; public PluginService(Environment environment, Settings settings, ServiceManager serviceManager) { super(environment, settings, serviceManager); pluginAnalyzerFactoryManager = new PluginAnalyzerFactoryManager(); // irservice에 전달되는 객체이므로 삭제하지 말고 계속사용한다. } @Override protected boolean doStart() throws FastcatSearchException { pluginAnalyzerFactoryManager.clear(); pluginMap = new HashMap<String, Plugin>(); // 플러그인을 검색하여 // 무작위로 시작한다. File pluginRootDir = environment.filePaths().file("plugin"); if (!pluginRootDir.exists()) { logger.info("플러그인 디렉토리가 없어서 플러그인을 로딩하지 않습니다. {}", pluginRootDir.getAbsolutePath()); return true; } List<File> pluginDirList = new ArrayList<File>(); findPluginDirectory(pluginRootDir, pluginDirList); // 모든 plugin의 jar파일을 로딩. int i = 0; List<File> analysisFiles = new ArrayList<File>(); for (File dir : pluginDirList) { File[] jarFiles = findFiles(dir, "jar"); logger.debug("FOUND plugin {}, jar={}", dir.getAbsolutePath(), jarFiles); // analysis 라이브러리는 모두 묶어서 한번에 로딩한다. 서로 dependency가 존재할수 있기때문에.. if (dir.getAbsolutePath().contains("analysis")) { // analysis logger.debug("analysis >> {}", dir.getAbsolutePath()); for (File f : jarFiles) { analysisFiles.add(f); } } else { String tag = i++ + "_plugin_" + dir.getName(); DynamicClassLoader.add(tag, jarFiles); logger.debug("Add plugin {}, jar={}", tag, jarFiles); } } // analyzer는 class가 서로의존관계가 있을수 있으므로, 한번에 묶어서 클래스패스잡음. if (analysisFiles.size() > 0) { DynamicClassLoader.add("plugin_analysis", analysisFiles); logger.debug("Add plugin plugin_analysis, jar={}", analysisFiles); } try { JAXBContext jc = JAXBContext.newInstance(DefaultPluginSetting.class); JAXBContext analysisJc = JAXBContext.newInstance(AnalysisPluginSetting.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); Unmarshaller analysisUnmarshaller = analysisJc.createUnmarshaller(); for (File dir : pluginDirList) { boolean isAnalysis = dir.getAbsolutePath().contains("analysis"); File pluginConfigFile = new File(dir, SettingFileNames.pluginConfig); if (!pluginConfigFile.exists()) { continue; } /* * 1. Plugin 객체생성. */ PluginSetting setting = null; try { InputStream is = new FileInputStream(pluginConfigFile); if (isAnalysis) { setting = (PluginSetting) analysisUnmarshaller.unmarshal(is); } else { setting = (PluginSetting) unmarshaller.unmarshal(is); } is.close(); logger.debug("PluginSetting >>> {}, {}", setting, pluginConfigFile.getAbsolutePath()); String className = setting.getClassName(); String pluginId = setting.getId(); Plugin plugin = null; if (className != null && className.length() > 0) { plugin = DynamicClassLoader.loadObject(className, Plugin.class, new Class<?>[]{File.class, PluginSetting.class, String.class}, new Object[]{dir, setting, environment.getServerId()}); if(plugin == null){ logger.error("Cannot load plugin {} : {}", pluginId, className); continue; } try{ plugin.load(environment.isMasterNode()); logger.debug("PLUGIN {} >> {}", setting.getId(), plugin.getClass().getName()); if (plugin instanceof AnalysisPlugin) { AnalysisPlugin analysisPlugin = (AnalysisPlugin) plugin; Map<String, AnalyzerInfo> map = analysisPlugin.analyzerFactoryMap(); for (Entry<String, AnalyzerInfo> entry : map.entrySet()) { pluginAnalyzerFactoryManager.addAnalyzerFactory(pluginId + "." + entry.getKey(), entry.getValue().factory()); } } } catch( LicenseInvalidException e ) { logger.error("License error! {}", e.getMessage()); ClusterAlertService.getInstance().alert(e); } pluginMap.put(setting.getId(), plugin); // } else { // plugin = new Plugin(dir, setting); } } catch (FileNotFoundException e) { logger.error("{} plugin 설정파일을 읽을수 없음.", dir.getName()); ClusterAlertService.getInstance().alert(e); } catch (JAXBException e) { logger.error("plugin 설정파일을 읽는중 에러. {}", e); ClusterAlertService.getInstance().alert(e); } catch (IOException e) { logger.error("{}", e); ClusterAlertService.getInstance().alert(e); } } } catch (Exception e) { throw new FastcatSearchException("ERR-00200", e); } return true; } private void findPluginDirectory(File pluginRootDir, List<File> pluginList) { File[] files = pluginRootDir.listFiles(); if (files == null) { return; } for (File file : files) { if (file.isDirectory()) { logger.debug("check dir {}", file.getAbsolutePath()); if (new File(file, SettingFileNames.pluginConfig).exists()) { pluginList.add(file); } findPluginDirectory(file, pluginList); } } } private File[] findFiles(File dir, String extension) { final String pattern = "." + extension; return dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name.endsWith(pattern)) { return true; } return false; } }); } @Override protected boolean doStop() throws FastcatSearchException { if (pluginMap != null) { for (Plugin plugin : pluginMap.values()) { try { plugin.unload(); } catch (Exception ignore) { } } pluginMap.clear(); pluginMap = null; } if (pluginAnalyzerFactoryManager != null) { pluginAnalyzerFactoryManager.clear(); } return true; } @Override protected boolean doClose() throws FastcatSearchException { doStop(); return true; } public Collection<Plugin> getPlugins() { return pluginMap.values(); } public Plugin getPlugin(String pluginId) { if(pluginId == null) { return null; } return pluginMap.get(pluginId.toUpperCase()); } public void loadAction() { HttpRequestService httpRequestService = ServiceManager.getInstance().getService(HttpRequestService.class); for (Plugin plugin : getPlugins()) { String pluginId = plugin.pluginId().toUpperCase(); List<Action> pluginActionList = plugin.getPluginSetting().getActionList(); if (pluginActionList != null && pluginActionList.size() > 0) { for (Action pluginAction : pluginActionList) { String className = pluginAction.getClassName(); if (className == null || className.length() == 0) { logger.warn("Plugin {} action class name is empty.", pluginId); continue; } className = className.trim(); httpRequestService.registerAction(className, pluginActionPrefix + pluginId); } } } } public void loadSchedule() { if (environment.isMasterNode()) { JobService jobService = serviceManager.getService(JobService.class); for (Plugin plugin : getPlugins()) { List<PluginSchedule> pluginScheduleList = plugin.getPluginSetting().getScheduleList(); if (pluginScheduleList != null && pluginScheduleList.size() > 0) { for (PluginSchedule pluginSchedule : pluginScheduleList) { Job job = DynamicClassLoader.loadObject(pluginSchedule.getClassName(), Job.class); if (job != null) { job.setArgs(pluginSchedule.getArgs()); try { jobService.schedule(job, Formatter.parseDate(pluginSchedule.getStartTime()), pluginSchedule.getPeriodInMinute() * 60); } catch (ParseException e) { logger.error("Error parsing plugin schedule {} : {}", pluginSchedule.getStartTime(), e); } } else { logger.error("PluginSchedule job is null >> {}", job); } } } } } else { logger.info("PluginService Schdule is not started. Because it's not master node. {}", environment.myNodeId()); } } @Override public AnalyzerFactoryManager getAnalyzerFactoryManager() { return pluginAnalyzerFactoryManager; } }