package com.kenai.redminenb; import com.kenai.redminenb.query.ParameterValue; import com.kenai.redminenb.query.RedmineQuery; import com.kenai.redminenb.query.serialization.RedmineQueryXml; import com.kenai.redminenb.repository.RedmineRepository; import com.kenai.redminenb.ui.Defaults; import com.taskadapter.redmineapi.RedmineException; import com.taskadapter.redmineapi.bean.Project; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.awt.Image; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.Icon; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.NbPreferences; /** * * @author Mykolas * @author Anchialas <anchialas@gmail.com> */ public class RedmineConfig { private static final Logger LOG = Logger.getLogger(RedmineConfig.class.getName()); private static final String REPO_ID = "redmine.repository"; // NOI18N private static final String QUERY_NAME = "redmine.query_"; // NOI18N private static final String QUERY_REFRESH_INT = "redmine.query_refresh"; // NOI18N private static final String QUERY_AUTO_REFRESH = "redmine.query_auto_refresh_"; // NOI18N private static final String ISSUE_REFRESH_INT = "redmine.issue_refresh"; // NOI18N private static final String DELIMITER = "<=>"; // NOI18N private static final String CHECK_UPDATES = "redmine.check_updates"; // NOI18N private static final String LAST_CHANGE_FROM = "redmine.last_change_from"; // NOI18N private static final String ACTIONITEMISSUES_STORAGE = "actionitemissues"; //NOI18N private static final String ACTIONITEMISSUES_STORAGE_FILE = ACTIONITEMISSUES_STORAGE + ".data"; //NOI18N // public static final int DEFAULT_QUERY_REFRESH = 30; public static final int DEFAULT_ISSUE_REFRESH = 15; // private static final JAXBContext jaxbContext; static { JAXBContext tempJaxbContext = null; try { tempJaxbContext = JAXBContext.newInstance( "com.kenai.redminenb.query.serialization", RedmineConfig.class.getClassLoader()); } catch (JAXBException ex) { LOG.log(Level.WARNING, "Failed to initialize MantisQuery saving", ex); } jaxbContext = tempJaxbContext; } // private Map<String, Icon> priorityIcons; private Map<String, Image> priorityImages; public static RedmineConfig getInstance() { return LazyHolder.INSTANCE; } private RedmineConfig() { // suppressed for non-instantiability } private Preferences getPreferences() { return NbPreferences.forModule(RedmineConfig.class); } public void setQueryRefreshInterval(int i) { getPreferences().putInt(QUERY_REFRESH_INT, i); } public void setIssueRefreshInterval(int i) { getPreferences().putInt(ISSUE_REFRESH_INT, i); } public void setCheckUpdates(boolean bl) { getPreferences().putBoolean(CHECK_UPDATES, bl); } public int getIssueRefreshInterval() { return getPreferences().getInt(ISSUE_REFRESH_INT, DEFAULT_ISSUE_REFRESH); } public void setQueryAutoRefresh(String queryName, boolean refresh) { getPreferences().putBoolean(QUERY_AUTO_REFRESH + queryName, refresh); } public int getQueryRefreshInterval() { return getPreferences().getInt(QUERY_REFRESH_INT, DEFAULT_QUERY_REFRESH); } public boolean getQueryAutoRefresh(String queryName) { return getPreferences().getBoolean(QUERY_AUTO_REFRESH + queryName, false); } public void putQuery(RedmineRepository repository, RedmineQuery query) { putQuery(repository, new RedmineQueryXml(query), query.getDisplayName()); } private void putQuery(RedmineRepository repository, RedmineQueryXml xml, String name) { try (StringWriter sw = new StringWriter()) { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.marshal(xml, sw); getPreferences().put(getQueryKey(repository.getID(), name), sw.toString()); } catch (JAXBException ex) { LOG.log(Level.WARNING, "Failed to serialize data", ex); } catch (IOException ex) { LOG.log(Level.WARNING, "Could not open file: {0}", ex); } } public void removeQuery(RedmineRepository repository, String displayName) { getPreferences().remove(getQueryKey(repository.getID(), displayName)); } public String[] getQueries(String repoID) { return getKeysWithPrefix(QUERY_NAME + repoID + DELIMITER); } private String getQueryKey(String repositoryID, String queryName) { return QUERY_NAME + repositoryID + DELIMITER + queryName; } private String getStoredQuery(RedmineRepository repository, String queryName) { return getPreferences().get(getQueryKey(repository.getID(), queryName), null); } public void reloadQuery(RedmineQuery rq) { RedmineQueryXml rqx = loadSerializedQuery(rq.getRepository(), rq.getDisplayName()); if (rqx == null) { return; } rqx.toRedmineQuery(rq); } public RedmineQuery getQuery(RedmineRepository repository, String queryName) { RedmineQueryXml rqx = loadSerializedQuery(repository, queryName); if(rqx == null) { return null; } RedmineQuery rq = new RedmineQuery(repository); rq.setName(queryName); rqx.toRedmineQuery(rq); return rq; } private RedmineQueryXml loadSerializedQuery(RedmineRepository repository, String queryName) { String value = getStoredQuery(repository, queryName); if (value == null) { return null; } try (StringWriter sw = new StringWriter()) { Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); Object o = unmarshaller.unmarshal(new StringReader(value)); if (o instanceof RedmineQueryXml) { RedmineQueryXml rqx = (RedmineQueryXml) o; boolean modified = false; // Version 2 of the serialization format introduced the project // parameter, that was previously taken from the project settings // this conversion sets the project based on the if(rqx.getVersion() == 1) { Project p = repository.getProject(); if( p != null && (! rqx.getParameters().containsKey("project_id"))) { rqx.getParameters().put("project_id", new ParameterValue[]{ new ParameterValue(p.getName(), p.getId()) }); modified = true; } } if(modified) { putQuery(repository, rqx, queryName); } return rqx; } } catch (JAXBException ex) { LOG.log(Level.WARNING, "Failed to serialize data", ex); } catch (IOException ex) { LOG.log(Level.WARNING, "Could not open file: {0}", ex); } return null; } public boolean getCheckUpdates() { return getPreferences().getBoolean(CHECK_UPDATES, true); } private String[] getKeysWithPrefix(String prefix) { String[] keys = null; try { keys = getPreferences().keys(); } catch (BackingStoreException ex) { Redmine.LOG.log(Level.SEVERE, null, ex); // XXX } if (keys == null || keys.length == 0) { return new String[0]; } List<String> ret = new ArrayList<>(); for (String key : keys) { if (key.startsWith(prefix)) { ret.add(key.substring(prefix.length())); } } return ret.toArray(keys); } public void setLastChangeFrom(String value) { getPreferences().put(LAST_CHANGE_FROM, value); } public String getLastChangeFrom() { return getPreferences().get(LAST_CHANGE_FROM, ""); // NOI18N } public Icon getPriorityIcon(String priorityName) { if (priorityIcons == null) { priorityIcons = new HashMap<>(); priorityIcons.put("Immediate", Defaults.getIcon("blocker.png")); // NOI18N priorityIcons.put("Urgent", Defaults.getIcon("critical.png")); // NOI18N priorityIcons.put("High", Defaults.getIcon("major.png")); // NOI18N priorityIcons.put("Normal", Defaults.getIcon("arrow_right.png")); // NOI18N priorityIcons.put("Low", Defaults.getIcon("minor.png")); // NOI18N } return priorityIcons.get(priorityName); } public Image getPriorityImage(String priorityName) { if (priorityImages == null) { priorityImages = new HashMap<>(); priorityImages.put("Immediate", Defaults.getImage("blocker.png")); // NOI18N priorityImages.put("Urgent", Defaults.getImage("critical.png")); // NOI18N priorityImages.put("High", Defaults.getImage("major.png")); // NOI18N priorityImages.put("Normal", Defaults.getImage("arrow_right.png")); // NOI18N priorityImages.put("Low", Defaults.getImage("minor.png")); // NOI18N } return priorityImages.get(priorityName); } /** * Saves issue ActionItem's permanently. * * @param issues */ @SuppressFBWarnings(value="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "If mkdirs fails it is checked one line further") public void setActionItemIssues(HashMap<String, List<String>> issues) { Redmine.LOG.fine("setActionItemIssues: saving issues"); //NOI18N File f = new File(getConfigPath()); f.mkdirs(); if (!f.canWrite()) { Redmine.LOG.warning("setActionItemIssues: Cannot create perm storage"); //NOI18N return; } ObjectOutputStream out = null; File file = new File(f, ACTIONITEMISSUES_STORAGE + ".tmp"); boolean success = false; try { // saving to a temp file out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file))); out.writeInt(issues.size()); for (Map.Entry<String, List<String>> entry : issues.entrySet()) { out.writeUTF(entry.getKey()); out.writeInt(entry.getValue().size()); for (String issueAttributes : entry.getValue()) { out.writeUTF(issueAttributes); } } success = true; } catch (IOException ex) { Redmine.LOG.log(Level.FINE, null, ex); } finally { if (out != null) { try { out.close(); } catch (IOException e) { } } } if (success) { // rename the temp file to the permanent one FileLock lock = null; try { FileObject foStorage = FileUtil.toFileObject(file); lock = foStorage.lock(); FileUtil.toFileObject(file).rename(lock, ACTIONITEMISSUES_STORAGE, "data"); } catch (IOException ex) { Redmine.LOG.log(Level.FINE, null, ex); success = false; } finally { if (lock != null) { lock.releaseLock(); } } } if (!success) { Redmine.LOG.warning("setActionItemIssues: could not save issues"); //NOI18N if (!file.delete()) { file.deleteOnExit(); } } } /** * Loads issues from a permanent storage * * @return */ public Map<String, List<String>> getActionItemIssues() { Redmine.LOG.fine("loadActionItemIssues: loading issues"); //NOI18N File f = new File(getConfigPath()); ObjectInputStream ois = null; File file = new File(f, ACTIONITEMISSUES_STORAGE_FILE); if (!file.canRead()) { Redmine.LOG.fine("loadActionItemIssues: no saved data"); //NOI18N return Collections.emptyMap(); } try { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file))); int size = ois.readInt(); Redmine.LOG.log(Level.FINE, "loadActionItemIssues: loading {0} records", size); //NOI18N HashMap<String, List<String>> issuesPerRepo = new HashMap<>(size); while (size-- > 0) { String repoUrl = ois.readUTF(); Redmine.LOG.log(Level.FINE, "loadActionItemIssues: loading issues for {0}", repoUrl); //NOI18N int issueCount = ois.readInt(); Redmine.LOG.log(Level.FINE, "loadActionItemIssues: loading {0} issues", issueCount); //NOI18N LinkedList<String> issues = new LinkedList<>(); while (issueCount-- > 0) { issues.add(ois.readUTF()); } issuesPerRepo.put(repoUrl, issues); } return issuesPerRepo; } catch (IOException ex) { Redmine.LOG.log(Level.FINE, null, ex); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { } } } return Collections.emptyMap(); } /** * Returns the path for the Redmine configuration directory. * * @return the path */ private static String getConfigPath() { //T9Y - nb redmine confing should be changable String t9yNbConfigPath = System.getProperty("netbeans.t9y.redmine.nb.config.path"); //NOI18N if (t9yNbConfigPath != null && t9yNbConfigPath.length() > 0) { return t9yNbConfigPath; } String nbHome = System.getProperty("netbeans.user"); //NOI18N return nbHome + "/config/issue-tracking/com-kenai-redminenb"; //NOI18N } private static class LazyHolder { private static final RedmineConfig INSTANCE = new RedmineConfig(); } }