001    package org.hackystat.telemetry.analyzer.configuration;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.Map;
006    import java.util.TreeMap;
007    
008    import org.hackystat.sensorbase.resource.users.jaxb.User;
009    
010    /**
011     * The manager for <code>TelemetryDefinitionInfo</code> objects. 
012     * <p> 
013     * Warning: This class is NOT thread-safe.
014     * <p>
015     * V8 Notes: If/when we decide to provide project-level sharing, we need to enforce the
016     * condition that only one instance of a name can exist for a given project. Right now it
017     * appears that multiple users can create telemetry definitions with the same name with 
018     * project scope.
019     * <p>
020     * I wonder why this class is not thread safe.  Perhaps synchronization occurs at a higher
021     * level. 
022     * 
023     * @author (Cedric) Qin Zhang
024     */
025    class TelemetryDefinitionInfoRepository {
026    
027      /**
028       * Two level map storing <code>TelemetryDefInfo</code> objects. The first
029       * level is keyed by userEmail (the owner), and the second level is keyed by the
030       * name of the telemetry definition object.
031       */
032      private Map<String, Map<String, TelemetryDefinitionInfo>> ownerKeyDefMap = 
033        new TreeMap<String, Map<String, TelemetryDefinitionInfo>>();
034    
035      /**
036       * Find the telemetry definition object by name. This method returns null if
037       * the telemetry definition cannot be found.
038       * <p>
039       * <b>Important</b> Note that it's possible that there are multiple
040       * definitions with the same name (all have project level sharing, but owned
041       * by different users). In this case, any one of the definitions might be
042       * returned. The only guarantee is that if there are definitions in the
043       * project share scope and global share scope with the same name, the one in
044       * project share scope will be returned.
045       * 
046       * @param owner The owner under which to find the telemetry definition object.
047       * @param name The name of the telemetry definition.
048       * @param includeShared If true, then those telemetry definitions owned by
049       *        other users, but is shared will also be returned.
050       * 
051       * @return An instance of <code>TelemetryDefInfo</code> object if found, or null.
052       */
053      TelemetryDefinitionInfo find(User owner, String name, boolean includeShared) {
054        TelemetryDefinitionInfo result = null;
055    
056        // find whether there is any definition owned by this user
057        Map<String, TelemetryDefinitionInfo> secondLevelMap = 
058          this.ownerKeyDefMap.get(owner.getEmail());
059        if (secondLevelMap != null) {
060          result = secondLevelMap.get(name);
061        }
062    
063        if (result == null && includeShared) {
064          // cannot find definition owned by this user, now search all definitions
065          // accessible to this user.
066          TelemetryDefinitionInfo globalDefInfo = null;
067          for (TelemetryDefinitionInfo theDefInfo : this.findAll(owner, true)) {
068            if (theDefInfo.getName().equals(name)) {
069              if (theDefInfo.getShareScope().isGlobal()) {
070                globalDefInfo = theDefInfo;
071              }
072              else {
073                result = theDefInfo;
074                break;
075              }
076            }
077          }
078          if (result == null) {
079            result = globalDefInfo;
080          }
081        }
082    
083        return result;
084      }
085    
086      /**
087       * Gets a list of telemetry definitions that this user has access to.
088       * 
089       * @param owner The owner of the telemetry definitions returned.
090       * @param includesShared If true, then those telemetry definitions owned by
091       *          other users, but is shared will also be returned.
092       * 
093       * @return A collection of <code>TelemetryDefInfo</code> objects.
094       */
095      Collection<TelemetryDefinitionInfo> findAll(User owner, boolean includesShared) {
096        if (!includesShared) { //NOPMD
097          Map<String, TelemetryDefinitionInfo> secondLevelMap =  
098            this.ownerKeyDefMap.get(owner.getEmail());
099          return secondLevelMap == null ? 
100              new ArrayList<TelemetryDefinitionInfo>(0) : secondLevelMap.values();
101        }
102        else {
103          // since shared defs need to be included, we need to scan everything.
104          ArrayList<TelemetryDefinitionInfo> list = new ArrayList<TelemetryDefinitionInfo>();
105          for (Map<String, TelemetryDefinitionInfo> secondLevelMap : this.ownerKeyDefMap.values()) {
106            for (TelemetryDefinitionInfo defInfo : secondLevelMap.values()) {
107              if (owner.equals(defInfo.getOwner())) {
108                list.add(defInfo);
109              }
110              else {
111                ShareScope shareScope = defInfo.getShareScope();
112                if (shareScope.isGlobal()) {
113                  list.add(defInfo);
114                }
115                else if (shareScope.isProject()) {
116                  try {
117                    if (shareScope.getProject().getMembers().getMember().contains(owner)) {
118                      list.add(defInfo);
119                    }
120                  }
121                  catch (TelemetryConfigurationException ex) {
122                    // should not happen, since we have already checked isProject()
123                    throw new RuntimeException(ex);
124                  }
125                }
126              }
127            }
128          }
129          return list;
130        }
131      }
132    
133      /**
134       * Adds a telemetry definition to this in-memory repository.
135       * 
136       * @param telemetryDefInfo Information about the definition to be added.
137       * @throws TelemetryConfigurationException If there is duplicated definition.
138       */
139      void add(TelemetryDefinitionInfo telemetryDefInfo) throws TelemetryConfigurationException {
140        User owner = telemetryDefInfo.getOwner();
141        Map<String, TelemetryDefinitionInfo> secondLevelMap = 
142          this.ownerKeyDefMap.get(owner.getEmail());
143        if (secondLevelMap == null) {
144          secondLevelMap = new TreeMap<String, TelemetryDefinitionInfo>();
145          this.ownerKeyDefMap.put(owner.getEmail(), secondLevelMap);
146        }
147    
148        String name = telemetryDefInfo.getName();
149        if (secondLevelMap.containsKey(name)) {
150          throw new TelemetryConfigurationException("User " + owner.toString()
151              + " already has telemetry definition " + name + " defined.");
152        }
153        secondLevelMap.put(name, telemetryDefInfo);
154      }
155    
156      /**
157       * Goes through all users, and checks whether there is a definition by name.
158       * 
159       * @param telemetryDefinitionName The definition name.
160       * @return True if it exists.
161       */
162      boolean exists(String telemetryDefinitionName) {
163        boolean found = false;
164        for (Map<String, TelemetryDefinitionInfo> secondLevelMap : this.ownerKeyDefMap.values()) {
165          if (secondLevelMap.containsKey(telemetryDefinitionName)) {
166            found = true;
167            break;
168          }
169        }
170        return found;
171      }
172    
173      /**
174       * Deletes a telemetry object definition. Only the owner can make the
175       * deletion. This method does nothing if the definition does not exist.
176       * 
177       * @param owner The owner of the definition.
178       * @param telemetryDefinitionName The name of the definition.
179       */
180      void remove(User owner, String telemetryDefinitionName) {
181        Map<String, TelemetryDefinitionInfo> secondLevelMap = this.ownerKeyDefMap.get(owner.getEmail());
182        if (secondLevelMap != null) {
183          secondLevelMap.remove(telemetryDefinitionName);
184        }
185      }
186    }