001    package org.hackystat.sensorshell.usermap;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.HashMap;
006    import java.util.HashSet;
007    import java.util.List;
008    import java.util.Locale;
009    import java.util.Map;
010    import java.util.Set;
011    
012    import javax.xml.bind.JAXBContext;
013    import javax.xml.bind.JAXBException;
014    import javax.xml.bind.Unmarshaller;
015    
016    import org.hackystat.sensorbase.client.SensorBaseClient;
017    import org.hackystat.sensorshell.usermap.resource.jaxb.ObjectFactory;
018    import org.hackystat.sensorshell.usermap.resource.jaxb.User;
019    import org.hackystat.sensorshell.usermap.resource.jaxb.Usermap;
020    import org.hackystat.sensorshell.usermap.resource.jaxb.Usermaps;
021    import org.hackystat.utilities.home.HackystatUserHome;
022    
023    /**
024     * Gets hackystat information for a specific tool by parsing a predefined xml file located in the
025     * user's .hackystat/usermap directory. The file should be named 'UserMaps.xml'. It is important to
026     * keep in mind that this code is working client-side, not on the Hackystat server.
027     * 
028     * <p>
029     * Instantiation of <code>UserMap</code> will parse the assumed xml file and derive a mapping from
030     * specific tool accounts for users to their Hackystat information.
031     * 
032     * <p>
033     * Developers should not have to directly use this class at all. They should only have to interface
034     * with the <code>SensorShellMap</code> class which manages an instance of this class.
035     * <p>
036     * If the UserMap.xml file does not exist, an empty UserMap is returned.
037     * <p>
038     * The "tool" string is always compared in a case-insensitive fashion. The User, ToolAccount,
039     * Password, and Sensorbase are case-sensitive.
040     * 
041     * @author Julie Ann Sakuda
042     */
043    class UserMap {
044    
045      /** Keys in the user map used to access information. */
046      enum UserMapKey {
047        /** The key for accessing the user value. */
048        USER,
049        /** The key for accessing the password value. */
050        PASSWORD,
051        /** The key for accessing the sensorbase value. */
052        SENSORBASE
053      }
054    
055      /** Three-key map for storing mappings for tool, toolaccount, user, password, and sensorbase. */
056      private Map<String, Map<String, Map<UserMapKey, String>>> userMappings;
057    
058      /** The (potentially non-existent) UserMap.xml file. */
059      private File userMapFile = null;
060    
061      /**
062       * Creates the user map and initializes the user mappings. Returns an empty UserMap if the
063       * UserMap.xml file cannot be found.
064       * 
065       * @throws SensorShellMapException Thrown if the UserMap.xml cannot be parsed.
066       */
067      UserMap() throws SensorShellMapException {
068        this.userMappings = new HashMap<String, Map<String, Map<UserMapKey, String>>>();
069    
070        File sensorPropsDir = new File(HackystatUserHome.getHome(), "/.hackystat/sensorshell/");
071        String userMapFilePath = sensorPropsDir.getAbsolutePath() + "/usermap/UserMap.xml";
072        this.userMapFile = new File(userMapFilePath);
073        if (userMapFile.exists()) {
074          this.loadUserMapFile(userMapFile);
075        }
076      }
077    
078      /**
079       * A method that will check all of the mappings associated with the given tool and throw an
080       * error if (a) any of the sensorbases could not be contacted, and/or (b) any of the users 
081       * did not appear to be registered.
082       * @param tool The tool of interest. 
083       * @throws SensorShellMapException The exception thrown if any errors are discovered. 
084       */
085      public void validateHackystatUsers(String tool) throws SensorShellMapException {
086        List<String> invalidSensorBases = new ArrayList<String>();
087        List<String> invalidUsers = new ArrayList<String>();
088        Map<String, Map<UserMapKey, String>> toolAccountMap = userMappings.get(tool);
089        // If there are no mappings for this tool, then return. 
090        if (toolAccountMap == null) {
091          return;
092        }
093        // Check all mappings associated with this tool.
094        for (Map<UserMapKey, String> userMap : toolAccountMap.values()) {
095          if (userMap == null) {
096            return;
097          }
098          String user = userMap.get(UserMapKey.USER);
099          String sensorbase = userMap.get(UserMapKey.SENSORBASE);
100          String password = userMap.get(UserMapKey.PASSWORD);
101          // Ignore this entire entry if we've already determined the sensorbase to be invalid.
102          if (invalidSensorBases.contains(sensorbase)) {
103            continue;
104          }
105          // Now check to see if it's validated. 
106          if (!SensorBaseClient.isHost(sensorbase)) {
107            invalidSensorBases.add(sensorbase);
108            // No use doing anything else.
109            continue;
110          }
111          // If we get here, it's a valid sensorbase. Now check to see if the user is OK.
112          if (!SensorBaseClient.isRegistered(sensorbase, user, password)) {
113            invalidUsers.add(user);
114          }
115        }
116    
117        // If all mappings are OK, we can return right now. 
118        if (invalidSensorBases.isEmpty() && invalidUsers.isEmpty()) {
119          return;
120        }
121    
122        // Otherwise throw an exception indicating the problem(s). Create the message. 
123        StringBuffer buff = new StringBuffer(20);
124        buff.append("Errors found in ").append(this.userMapFile.getAbsolutePath()).append(". ");
125        if (!invalidSensorBases.isEmpty()) {
126          buff.append("The following SensorBase hosts were not found or available: ");
127          for (String badBase : invalidSensorBases) {
128            buff.append(badBase).append(' ');
129          }
130        }
131        if (!invalidUsers.isEmpty()) {
132          buff.append("The following users did not appear to be valid: ");
133          for (String badUser : invalidUsers) {
134            buff.append(badUser).append(' ');
135          }
136        }
137        throw new SensorShellMapException(buff.toString());
138      }
139    
140      /**
141       * This constructor initializes the user mappings from a given file. This version of the
142       * constructor is mainly useful for junit test cases that want to verify the content of a dummy
143       * UserMap.xml file.
144       * 
145       * @param userMapFile A UserMap.xml file.
146       * @exception SensorShellMapException Occurs if UserMap.xml is invalid.
147       */
148      UserMap(File userMapFile) throws SensorShellMapException {
149        this.userMappings = new HashMap<String, Map<String, Map<UserMapKey, String>>>();
150        this.loadUserMapFile(userMapFile);
151      }
152    
153      /**
154       * Uses JAXB to read through the UserMap.xml file and add all information to the user map.
155       * 
156       * @param userMapFile The UserMap.xml file.
157       * @throws SensorShellMapException Thrown if JAXB encounters an error reading the xml file.
158       */
159      private void loadUserMapFile(File userMapFile) throws SensorShellMapException {
160        try {
161          JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
162          Unmarshaller unmarshaller = context.createUnmarshaller();
163    
164          Usermaps usermaps = (Usermaps) unmarshaller.unmarshal(userMapFile);
165          List<Usermap> usermapList = usermaps.getUsermap();
166          for (Usermap usermap : usermapList) {
167            // user lowercase tool for case-insensitive comparison
168            String tool = usermap.getTool().toLowerCase();
169            List<User> userList = usermap.getUser();
170            for (User user : userList) {
171              // Lowercase the toolaccount for case-insensitive comparison.
172              String toolAccount = user.getToolaccount().toLowerCase();
173    
174              String userName = user.getUser();
175              this.put(tool, toolAccount, UserMapKey.USER, userName);
176    
177              String password = user.getPassword();
178              this.put(tool, toolAccount, UserMapKey.PASSWORD, password);
179    
180              String sensorbase = user.getSensorbase();
181              this.put(tool, toolAccount, UserMapKey.SENSORBASE, sensorbase);
182            }
183          }
184        }
185        catch (JAXBException e) {
186          throw new SensorShellMapException("Error reading UserMap.xml file.", e);
187        }
188      }
189    
190      /**
191       * Puts a single user map entry for user, password, or sensorbase into the three-key map.
192       * 
193       * @param tool The tool the mapping is for.
194       * @param toolAccount The tool account for the mapping.
195       * @param key Either user, password, or sensorbase key.
196       * @param value The value associated with the key given.
197       */
198      private void put(String tool, String toolAccount, UserMapKey key, String value) {
199        if (!this.userMappings.containsKey(tool.toLowerCase(Locale.ENGLISH))) {
200          this.userMappings.put(tool.toLowerCase(Locale.ENGLISH), 
201              new HashMap<String, Map<UserMapKey, String>>());
202        }
203    
204        Map<String, Map<UserMapKey, String>> toolMapping = this.userMappings.get(tool);
205        if (!toolMapping.containsKey(toolAccount.toLowerCase(Locale.ENGLISH))) {
206          toolMapping.put(toolAccount.toLowerCase(Locale.ENGLISH), 
207              new HashMap<UserMapKey, String>());
208        }
209    
210        Map<UserMapKey, String> toolAccountMapping = 
211          toolMapping.get(toolAccount.toLowerCase(Locale.ENGLISH));
212        toolAccountMapping.put(key, value);
213      }
214    
215      /**
216       * Gets the value of the given <code>UserMapKey</code> associated with the given tool and
217       * toolAccount, or null if not found.
218       * 
219       * @param tool The tool name. This is case-insensitive.
220       * @param toolAccount The tool account name.  This is case-insensitive.
221       * @param key The USER, PASSWORD, or SENSORBASE key representing the desired value.
222       * @return Returns the value matching the criteria given or null if none can be found.
223       */
224      String get(String tool, String toolAccount, UserMapKey key) {
225        if (tool == null) {
226          return null;
227        }
228        String lowercasetool = tool.toLowerCase(Locale.ENGLISH);
229        String lowercaseaccount = toolAccount.toLowerCase(Locale.ENGLISH);
230        if (this.userMappings.containsKey(lowercasetool)) {
231          Map<String, Map<UserMapKey, String>> toolMapping = this.userMappings.get(lowercasetool);
232          if (toolMapping.containsKey(lowercaseaccount)) {
233            Map<UserMapKey, String> toolAccountMapping = toolMapping.get(lowercaseaccount);
234            return toolAccountMapping.get(key);
235          }
236        }
237        return null;
238      }
239    
240      /**
241       * Returns true if there is a defined userKey for the given Tool and ToolAccount.
242       * 
243       * @param tool A Tool, such as "Jira".
244       * @param toolAccount A Tool account, such as "johnson".
245       * @return True if the toolAccount is defined for the given tool in this userMap.
246       */
247      boolean hasUser(String tool, String toolAccount) {
248        // Lowercase the tool if possible.
249        if (tool == null) {
250          return false;
251        }
252        String lowercaseTool = tool.toLowerCase(Locale.ENGLISH);
253        String lowercaseAccount = toolAccount.toLowerCase(Locale.ENGLISH);
254        if (this.userMappings.containsKey(lowercaseTool)) {
255          Map<String, Map<UserMapKey, String>> toolMapping = this.userMappings.get(lowercaseTool);
256          return toolMapping.containsKey(lowercaseAccount);
257        }
258        return false;
259      }
260    
261      /**
262       * Returns the usermap.xml file path, which may or may not exist.
263       * 
264       * @return The usermap.xml file path.
265       */
266      String getUserMapFile() {
267        return this.userMapFile.getAbsolutePath();
268      }
269    
270      /**
271       * Returns the set of tool account names for the passed tool.
272       * 
273       * @param tool The tool of interest.
274       * @return The tool account names.
275       */
276      Set<String> getToolAccounts(String tool) {
277        String lowerCaseTool = tool.toLowerCase(Locale.ENGLISH);
278        Set<String> toolAccounts = new HashSet<String>();
279        Map<String, Map<UserMapKey, String>> toolMapping = this.userMappings.get(lowerCaseTool);
280        if (toolMapping == null) {
281          return toolAccounts;
282        }
283        else {
284          toolAccounts.addAll(toolMapping.keySet());
285          return toolAccounts;
286        }
287      }
288    }