package com.wilutions.itol.db; import java.io.File; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.wilutions.joa.TaskPanePosition; import com.wilutions.joa.gson.GsonBuilderJoa; /** * An object of this class stores configuration options. * Options are first read from a file defined by system option "com.wilutions.itol.appConfig". * This file might contain the options that are the same for all users. In a second step, * options are read from %APPDATA%/manufacturerName/appName/user-profile.json. This options * shall be user related and have precedence over the application options. * When writing options, only the user-profile.json is updated. */ public class Config implements Serializable, Cloneable { private static final long serialVersionUID = -5838975424557674776L; /** * System option that specifies the applications configuration file. * This system option is optional. If not set, only the user related options are read. */ public final static String SYSTEM_OPTION_CONFIG_FILE = "com.wilutions.itol.config"; /** * Application configuration file name. * Options are read from this file before user related options are read. * User related options have precedence. * When configuration is written, this file keeps unchanged, file {@link #CONFIG_FILE_APPLICATION_TEMPLATE} * is written instead. */ public final static String CONFIG_FILE_APPLICATION_READ = "application.json"; /** * Write application related values into this file. */ public final static String CONFIG_FILE_APPLICATION_WRITE = "application.json.templ"; /** * User configuration file name. */ public final static String CONFIG_FILE_USER = "user.json"; /** * Placeholder for export directory. * Pass this placehoder to {@link #exportAttachmentsProgram} to reference the export directory. */ public final static String PLACEHODER_EXPORT_DIRECTORY = "${export.dir}"; /** * Placeholder for project ID. * Pass this placehoder to {@link #exportAttachmentsProgram} to reference the project ID. */ public final static String PLACEHODER_PROJECT_ID = "${project.id}"; /** * Placeholder for issue ID. * Pass this placehoder to {@link #exportAttachmentsProgram} to reference the issue ID. */ public final static String PLACEHODER_ISSUE_ID = "${issue.id}"; public final static String EXPORT_PROROGRAM_EXPLORER = "\"C:\\Windows\\explorer.exe\" \"" + PLACEHODER_EXPORT_DIRECTORY + "\""; public final static String EXPORT_PROGRAM_CMD = "C:\\Windows\\System32\\cmd.exe /C start \"" + PLACEHODER_ISSUE_ID + "\" /d \"" + PLACEHODER_EXPORT_DIRECTORY + "\""; public final static String EXPORT_PROGRAM_TOTALCMD = "\"C:\\Program Files\\totalcmd\\TOTALCMD64.exe\" /O /L=\"" + PLACEHODER_EXPORT_DIRECTORY + "\""; /** * Default export program. * This program is executed by default when attachments are exported. */ public final static String EXPORT_PROROGRAM_DEFAULT = EXPORT_PROROGRAM_EXPLORER; private transient String manufacturerName; private transient String appName; private transient File tempDirForSession; /** * Configuration file version. */ private int version = 1; // Application or user options. private String serviceUrl = ""; private String issueIdMailSubjectFormat = Property.ISSUE_ID_MAIL_SUBJECT_FORMAT_DEFAULT; private Boolean injectIssueIdIntoMailSubject = Boolean.FALSE; private IdName msgFileFormat = MsgFileFormat.DEFAULT; private String logLevel = "INFO"; private int nbOfSuggestions = 20; private String proxyServer = ""; private boolean proxyServerEnabled; private int proxyServerPort; private TaskPanePosition taskPanePosition; private int maxHistoryItems = 100; /** * Convert HTML mail body to markup. */ private MailBodyConversion mailBodyConversion = MailBodyConversion.MARKUP; /** * Timeout for converting mail body. * E.g. mails from ABBYY require more than 20s to be loaded. */ private int mailBodyConversionTimeoutSeconds = 10; /** * Custom field for mail address. */ private String autoReplyField = ""; /** * Start this program after attachments have been exported. */ private String exportAttachmentsProgram = ""; /** * Files with this extensions are always opened as text files. */ private String extensionsAlwaysOpenAsText = DEFAULT_BLACK_EXTENSIONS; /** * List of files that should not be added to an issue. */ private List<AttachmentBlacklistItem> blacklist = new ArrayList<>(); // https://www.howtogeek.com/137270/50-file-extensions-that-are-potentially-dangerous-on-windows/ private final static String DEFAULT_BLACK_EXTENSIONS = ".exe.pif.application.gadget.msi.msp.com.scr.hta.cpl.msc.jar.scf.lnk.inf" + ".reg.pl.bat.cmd.vb.vbs.js.jse.ws.wsf.wsc.wsh.ps1.ps1xml.ps2.ps2xml.psc1.psc2.msh.msh1.msh2.mshxml.msh1xml.msh2xml"; // Only user related options. private String userName = ""; private String encryptedPassword = ""; private String credentials = ""; private String logFile = ""; private String exportAttachmentsDirectory = ""; private String proxyServerUserName = ""; private String proxyServerEncryptedUserPassword = ""; private String tempDir = ""; public Config() { tempDir = new File(System.getProperty("java.io.tmpdir"), "ITOL").getAbsolutePath(); logFile = new File(System.getProperty("java.io.tmpdir"), "itol.log").getAbsolutePath(); exportAttachmentsDirectory = new File(tempDir, "Export").getAbsolutePath(); exportAttachmentsProgram = EXPORT_PROROGRAM_DEFAULT; userName = proxyServerUserName = System.getProperty("user.name"); taskPanePosition = new TaskPanePosition(); } @Override public Object clone() { Config config = new Config(); config.copyFrom(this); return config; } protected void copyFrom(Config rhs) { this.manufacturerName = rhs.manufacturerName; this.appName = rhs.appName; this.version = rhs.version; this.serviceUrl = rhs.serviceUrl; this.issueIdMailSubjectFormat = rhs.issueIdMailSubjectFormat; this.injectIssueIdIntoMailSubject = rhs.injectIssueIdIntoMailSubject; this.userName = rhs.userName; this.encryptedPassword = rhs.encryptedPassword; this.msgFileFormat = rhs.msgFileFormat; this.logLevel = rhs.logLevel; this.logFile = rhs.logFile; this.nbOfSuggestions = rhs.nbOfSuggestions; this.exportAttachmentsDirectory = rhs.exportAttachmentsDirectory; this.credentials = rhs.credentials; this.proxyServerUserName = rhs.proxyServerUserName; this.proxyServerEncryptedUserPassword = rhs.proxyServerEncryptedUserPassword; this.proxyServer = rhs.proxyServer; this.proxyServerEnabled = rhs.proxyServerEnabled; this.proxyServerPort = rhs.proxyServerPort; this.taskPanePosition = rhs.taskPanePosition; this.autoReplyField = rhs.autoReplyField; this.extensionsAlwaysOpenAsText = rhs.extensionsAlwaysOpenAsText; this.maxHistoryItems = rhs.maxHistoryItems; this.mailBodyConversion = rhs.mailBodyConversion; this.blacklist = new ArrayList<AttachmentBlacklistItem>(rhs.blacklist); this.exportAttachmentsProgram = rhs.exportAttachmentsProgram; } public static <T extends Config> T read(String manufacturerName, String appName, Class<T> clazz) throws Exception { T config = null; // Read application configuration File applicationConfigFile = getConfigFile(manufacturerName, appName, CONFIG_FILE_APPLICATION_READ); try { String optFile = System.getProperty(SYSTEM_OPTION_CONFIG_FILE); if (!Default.value(optFile).isEmpty()) { applicationConfigFile = new File(optFile); } System.out.println("INFO: Application configuration file=" + applicationConfigFile + ", exists=" + applicationConfigFile.exists()); if (applicationConfigFile.exists()) { config = read(applicationConfigFile, clazz); } } catch (Exception e) { System.out.println("WARN: Failed to read application configuration file=" + applicationConfigFile); e.printStackTrace(); } // Read user related configuration File userConfigFile = getConfigFile(manufacturerName, appName, CONFIG_FILE_USER); try { System.out.println("INFO: User configuration file=" + userConfigFile + ", exists=" + userConfigFile.exists()); if (userConfigFile.exists()) { T userConfig = read(userConfigFile, clazz); if (config != null) { config.copyNotEmptyFields(userConfig, clazz); } else { config = userConfig; } } } catch (Exception e) { System.out.println("WARN: Failed to read user configuration file=" + userConfigFile); e.printStackTrace(); } if (config == null) { System.out.println("INFO: Missing configuration files, use default configuration."); config = clazz.newInstance(); } config.setManufacturerName(manufacturerName); config.setAppName(appName); return config; } public void write() { // Write application related configuration. File applicationConfigFile = getConfigFile(manufacturerName, appName, CONFIG_FILE_APPLICATION_WRITE); try { System.out.println("INFO: write configuration file=" + applicationConfigFile); extractApplicationConfig().write(applicationConfigFile); } catch (Exception e) { System.out.println("ERROR: Failed to write application configuration file=" + applicationConfigFile); e.printStackTrace(); } // Write user related configuration File userConfigFile = getConfigFile(manufacturerName, appName, CONFIG_FILE_USER); try { System.out.println("INFO: Write configuration file=" + userConfigFile); write(userConfigFile); } catch (Exception e) { System.out.println("WARN: Failed to write user configuration file=" + userConfigFile); e.printStackTrace(); } } private static <T extends Config> T read(File configFile, Class<T> clazz) throws Exception { GsonBuilder builder = GsonBuilderJoa.create(); Gson gson = builder.create(); byte[] bytes = Files.readAllBytes(configFile.toPath()); return gson.fromJson(new String(bytes, "UTF-8"), clazz); } protected void copyNotEmptyFields(Object userConfig, Class<?> clazz) throws Exception { for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) continue; if (Modifier.isFinal(field.getModifiers())) continue; if (Modifier.isTransient(field.getModifiers())) continue; field.setAccessible(true); Object value = field.get(userConfig); if (value != null) { if (value instanceof String) { if (!((String)value).isEmpty()) { field.set(this, value); } } else if (value instanceof Collection) { if (!((Collection<?>)value).isEmpty()) { field.set(this, value); } } else { field.set(this, value); } } } if (clazz != Config.class) { copyNotEmptyFields(userConfig, clazz.getSuperclass()); } } private void write(File configFile) throws Exception { GsonBuilder builder = GsonBuilderJoa.create(); builder.setPrettyPrinting(); Gson gson = builder.create(); String json = gson.toJson(this); json = json.replaceAll("\n", "\r\n"); byte[] bytes = json.getBytes("UTF-8"); configFile.delete(); Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE_NEW); } protected Config extractApplicationConfig() { Config appConfig = (Config)this.clone(); // Unset user related properties. appConfig.setUserName(null); appConfig.setEncryptedPassword(null); appConfig.setLogFile(null); appConfig.setExportAttachmentsDirectory(null); appConfig.setCredentials(null); appConfig.setProxyServerUserName(null); appConfig.setProxyServerEncryptedUserPassword(null); return appConfig; } private static File getConfigFile(String manufacturerName, String appName, String fileName) { String appData = System.getenv("APPDATA"); if (Default.value(appData).isEmpty()) { appData = "."; } File dataDir = new File(new File(new File(appData), manufacturerName), appName).getAbsoluteFile(); dataDir.mkdirs(); File configFile = new File(dataDir, fileName); return configFile; } public String getManufacturerName() { return manufacturerName; } public void setManufacturerName(String manufacturerName) { this.manufacturerName = manufacturerName; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getServiceUrl() { return serviceUrl; } public void setServiceUrl(String serviceUrl) { this.serviceUrl = serviceUrl; } public String getIssueIdMailSubjectFormat() { return issueIdMailSubjectFormat; } public void setIssueIdMailSubjectFormat(String issueIdMailSubjectFormat) { this.issueIdMailSubjectFormat = issueIdMailSubjectFormat; } public boolean isInjectIssueIdIntoMailSubject() { return injectIssueIdIntoMailSubject; } public void setInjectIssueIdIntoMailSubject(boolean injectIssueIdIntoMailSubject) { this.injectIssueIdIntoMailSubject = injectIssueIdIntoMailSubject; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEncryptedPassword() { return encryptedPassword; } public void setEncryptedPassword(String encryptedPassword) { this.encryptedPassword = encryptedPassword; } public IdName getMsgFileFormat() { return msgFileFormat; } public void setMsgFileFormat(IdName msgFileFormat) { this.msgFileFormat = msgFileFormat; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public Boolean getInjectIssueIdIntoMailSubject() { return injectIssueIdIntoMailSubject; } public void setInjectIssueIdIntoMailSubject(Boolean injectIssueIdIntoMailSubject) { this.injectIssueIdIntoMailSubject = injectIssueIdIntoMailSubject; } public String getLogLevel() { return logLevel; } public void setLogLevel(String logLevel) { this.logLevel = logLevel; } public String getLogFile() { return logFile; } public void setLogFile(String logFile) { this.logFile = logFile; } public int getNbOfSuggestions() { return nbOfSuggestions; } public void setNbOfSuggestions(int nbOfSuggestions) { this.nbOfSuggestions = nbOfSuggestions; } public String getExportAttachmentsDirectory() { return exportAttachmentsDirectory; } public void setExportAttachmentsDirectory(String exportAttachmentsDirectory) { this.exportAttachmentsDirectory = exportAttachmentsDirectory; } public String getCredentials() { return credentials; } public void setCredentials(String credentials) { this.credentials = credentials; } public String getProxyServer() { if (proxyServer == null) proxyServer= ""; return proxyServer; } public void setProxyServer(String proxyServer) { this.proxyServer = proxyServer; } public boolean isProxyServerEnabled() { return proxyServerEnabled; } public void setProxyServerEnabled(boolean proxyServerEnabled) { this.proxyServerEnabled = proxyServerEnabled; } public int getProxyServerPort() { return proxyServerPort; } public void setProxyServerPort(int proxyServerPort) { this.proxyServerPort = proxyServerPort; } public String getProxyServerUserName() { return proxyServerUserName; } public void setProxyServerUserName(String proxyServerUserName) { this.proxyServerUserName = proxyServerUserName; } public String getProxyServerEncryptedUserPassword() { return proxyServerEncryptedUserPassword; } public void setProxyServerEncryptedUserPassword(String proxyServerEncryptedUserPassword) { this.proxyServerEncryptedUserPassword = proxyServerEncryptedUserPassword; } public TaskPanePosition getTaskPanePosition() { return taskPanePosition; } public void setTaskPanePosition(TaskPanePosition taskPanePosition) { this.taskPanePosition = taskPanePosition; } public String getAutoReplyField() { return autoReplyField; } public void setAutoReplyField(String autoReplyField) { this.autoReplyField = autoReplyField; } public String getTempDirBase() { return tempDir; } public void setTempDirBase(String tempDir) { this.tempDir = tempDir; } public synchronized File getTempDir() { if (tempDirForSession == null) { SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss"); tempDirForSession = new File(tempDir, dateTimeFormat.format(new Date(System.currentTimeMillis()))); tempDirForSession.mkdirs(); } return tempDirForSession; } public int getMaxHistoryItems() { return maxHistoryItems; } public void setMaxHistoryItems(int v) { maxHistoryItems = v; } public MailBodyConversion getMailBodyConversion() { return mailBodyConversion; } public void setMailBodyConversion(MailBodyConversion mailBodyConversion) { this.mailBodyConversion = mailBodyConversion; } public List<AttachmentBlacklistItem> getBlacklist() { return blacklist; } public void setBlacklist(List<AttachmentBlacklistItem> blacklist) { this.blacklist = blacklist; } public String getExtensionsAlwaysOpenAsText() { return extensionsAlwaysOpenAsText; } public void setExtensionsAlwaysOpenAsText(String extensionsAlwaysOpenAsText) { this.extensionsAlwaysOpenAsText = extensionsAlwaysOpenAsText; } public String getExportAttachmentsProgram() { return exportAttachmentsProgram; } public void setExportAttachmentsProgram(String exportAttachmentsProgram) { this.exportAttachmentsProgram = exportAttachmentsProgram; } public int getMailBodyConversionTimeoutSeconds() { return mailBodyConversionTimeoutSeconds; } public void setMailBodyConversionTimeoutSeconds(int mailBodyConversionTimeoutSeconds) { this.mailBodyConversionTimeoutSeconds = mailBodyConversionTimeoutSeconds; } }