View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  /**
17   *
18   */
19  package org.kuali.student.common.ui.client.mvc;
20  
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
28  import org.kuali.student.common.ui.client.mvc.ModelChangeEvent.Action;
29  import org.kuali.student.common.ui.client.validator.ClientDateParser;
30  import org.kuali.student.common.ui.client.validator.DataModelValidator;
31  import org.kuali.student.r1.common.assembly.data.Data;
32  import org.kuali.student.r1.common.assembly.data.Metadata;
33  import org.kuali.student.r1.common.assembly.data.ModelDefinition;
34  import org.kuali.student.r1.common.assembly.data.QueryPath;
35  import org.kuali.student.r1.common.assembly.data.Data.DataType;
36  import org.kuali.student.r1.common.assembly.data.Data.DataValue;
37  import org.kuali.student.r1.common.assembly.data.Data.Key;
38  import org.kuali.student.r1.common.assembly.data.Data.Property;
39  import org.kuali.student.r1.common.assembly.data.Data.Value;
40  import org.kuali.student.r1.common.assembly.data.HasChangeCallbacks.ChangeCallback;
41  import org.kuali.student.r1.common.assembly.data.HasChangeCallbacks.ChangeCallbackRegistration;
42  import org.kuali.student.r1.common.assembly.data.HasChangeCallbacks.ChangeType;
43  import org.kuali.student.r2.common.dto.ValidationResultInfo;
44  import org.kuali.student.r1.common.validator.DateParser;
45  
46  import com.google.gwt.core.client.GWT;
47  import com.google.gwt.event.shared.HandlerManager;
48  import com.google.gwt.event.shared.HandlerRegistration;
49  
50  /**
51   * The data model for Kuali Student.  Data is stored in a map of maps and is accessed through a QueryPath.
52   * 
53   * @author Kuali Student Team
54   * @see QueryPath
55   */
56  @SuppressWarnings("unchecked")
57  public class DataModel implements Model {
58      public interface QueryCallback<T> {
59          void onResult(QueryPath path, T result);
60      }
61  
62      private static final long serialVersionUID = 1L;
63  
64      private String modelName = "";
65      private ModelDefinition definition;
66      private DataModelValidator validator = new DataModelValidator();
67  
68      private HandlerManager handlers = new HandlerManager(this);
69      private ChangeCallbackRegistration bridgeCallbackReg;
70  
71      private Data root;
72  
73      private String parentPath;    //Set this if DataModel's root element is nested in another data element.
74  
75      public DataModel() {
76          // do nothing
77      }
78  
79      public DataModel(String name) {
80          this.modelName = name;
81      }
82  
83      public DataModel(final ModelDefinition definition, final Data root) {
84          this.definition = definition;
85          this.root = root;
86          validator.setDateParser((DateParser) GWT.create(ClientDateParser.class));
87      }
88  
89      public String getModelName() {
90          return modelName;
91      }
92  
93      public void setModelName(String modelName) {
94          this.modelName = modelName;
95      }
96  
97      public <T> T get(final QueryPath path) {
98          return (T) root.query(path);
99      }
100 
101     public <T> T get(final String path) {
102         return (T) get(QueryPath.parse(path));
103     }
104 
105     /**
106      * @return the root
107      */
108     public Data getRoot() {
109         return root;
110     }
111 
112     public void resetRoot() {
113         root = null;
114     }
115 
116     public void remove(final QueryPath path) {
117         QueryPath parent = null;
118         QueryPath leavePath = null;
119         if (path != null && path.size() >= 2) {
120             parent = path.subPath(0, path.size() - 1);
121             leavePath = path.subPath(path.size() - 1, path.size());
122             Object parentData = this.get(parent);
123             if (parentData != null && parentData instanceof Data) {
124                 ((Data) parentData).remove(
125                         new Data.StringKey(leavePath.toString()));
126             }
127 
128         } else if (path != null) {
129             root.remove(new Data.StringKey(path.toString()));
130         }
131     }
132 
133     /** 
134      * @param path The path in the data model
135      * @return A map containing the path/value pairs for all matching elements, or an empty
136      * map if no matching values found.
137      */
138     public Map<QueryPath, Object> query(final QueryPath path) {
139         Map<QueryPath, Object> result = new HashMap<QueryPath, Object>();
140         queryRelative(root, path, result);
141         return result;
142     }
143 
144     /** 
145      * @param path The path in the data model
146      * @return A map containing the path/value pairs for all matching elements, or an empty
147      * map if no matching values found.
148      */
149     public Map<QueryPath, Object> query(final String path) {
150         return query(QueryPath.parse(path));
151     }
152 
153     /**
154      * @param branch The data branch on which to perform the query
155      * @param path   The path string relative to the root of the branch
156      * @param result A map containing all matching paths and the corresponding data value. For a non-wildcard path
157      *               this should return a single entry in the map of the path supplied and the corresponding value. For
158      *               a path containing wildcard, an entry in the map will exist for each matching path and their corresponding values.
159      *               Note: A path ending in a wild card will not match the _runtimeData element.
160      */
161     private void queryRelative(final Data branch, final QueryPath path, Map<QueryPath, Object> result) {
162         //Should this add the entire branch to the result when query path is an empty string?
163 
164         Data d = branch;
165 
166         for (int i = 0; i < path.size(); i++) {
167             if (d == null) {
168                 // dead end
169                 break;
170             }
171             final Key key = path.get(i);
172             if (key.equals(Data.WILDCARD_KEY)) {
173                 final QueryPath relative = path.subPath(i + 1, path.size());
174                 if (!relative.isEmpty()) {
175                     //Handle remaining path after wildcard
176                     for (final Property p : d) {
177                         if (p.getValueType().equals(Data.class)) {
178                             queryRelative((Data) p.getValue(), relative, result);
179                         }
180                     }
181                 } else {
182                     //The wildcard is last element in path, so add all sub-elements to result
183                     //The wildcard will not match a _runtimeData path
184                     Set<Key> keys = d.keySet();
185                     for (Key wildcardKey : keys) {
186                         if (!("_runtimeData".equals(wildcardKey.get()))) {
187                             QueryPath wildcardPath = path.subPath(0, path.size() - 1);
188                             wildcardPath.add(wildcardKey);
189                             result.put(wildcardPath, d.get(wildcardKey));
190                         }
191                     }
192                 }
193                 break; //No need to continue once we process wildcard
194             } else if (i < path.size() - 1) {
195                 d = d.get(key);
196             } else {
197                 final QueryPath resultPath = d.getQueryPath();
198                 resultPath.add(key);
199 
200                 Object resultValue = d.get(key);
201 
202                 //If query is against DataModel whose root element is child of another data object, 
203                 //need to strip of the parent path so result path is relative to root of child element
204                 if (parentPath != null) {
205                     String relativePath = resultPath.toString();
206                     if (relativePath.contains("/")) {
207                         relativePath = relativePath.substring(parentPath.length());
208                         result.put(QueryPath.parse(relativePath), resultValue);
209                     }else{
210                        result.put(resultPath, resultValue); 
211                     }
212                 } else {
213                     result.put(resultPath, resultValue);
214                 }
215             }
216         }
217     }
218 
219     public void set(final QueryPath path, final Data value) {
220         set(path, new Data.DataValue(value));
221     }
222 
223     public void set(final QueryPath path, final Integer value) {
224         set(path, new Data.IntegerValue(value));
225     }
226 
227     public void set(final QueryPath path, final String value) {
228         set(path, new Data.StringValue(value));
229     }
230 
231     public void set(final QueryPath path, final Long value) {
232         set(path, new Data.LongValue(value));
233     }
234 
235     public void set(final QueryPath path, final Short value) {
236         set(path, new Data.ShortValue(value));
237     }
238 
239     public void set(final QueryPath path, final Double value) {
240         set(path, new Data.DoubleValue(value));
241     }
242 
243     public void set(final QueryPath path, final Float value) {
244         set(path, new Data.FloatValue(value));
245     }
246 
247     public void set(final QueryPath path, final Boolean value) {
248         set(path, new Data.BooleanValue(value));
249     }
250 
251     public void set(final QueryPath path, final Date value) {
252         set(path, new Data.DateValue(value));
253     }
254 
255     public void set(final QueryPath path, final Value value) {
256         definition.ensurePath(root, path, value instanceof DataValue);
257         if (path.size() > 1) {
258             final QueryPath q = path.subPath(0, path.size() - 1);
259             final Data d = root.query(q);
260             d.set(path.get(path.size() - 1), value);
261         } else {
262             root.set(path.get(0), value);
263         }
264     }
265 
266     public DataType getType(final QueryPath path) {
267         return definition.getType(path);
268     }
269 
270     public Metadata getMetadata(final QueryPath path) {
271         return definition.getMetadata(path);
272     }
273 
274     /**
275      * Set the top level data for this DataModel
276      * @param root the root to set
277      */
278     public void setRoot(final Data root) {
279         if (bridgeCallbackReg != null) {
280             bridgeCallbackReg.remove();
281         }
282         this.root = root;
283         bridgeCallbackReg = root.addChangeCallback(new ChangeCallback() {
284             @Override
285             public void onChange(ChangeType type, QueryPath path) {
286                 Action action = null;
287                 if (type == ChangeType.ADD) {
288                     action = Action.ADD;
289                 } else if (type == ChangeType.REMOVE) {
290                     action = Action.REMOVE;
291                 } else if (type == ChangeType.UPDATE) {
292                     action = Action.UPDATE;
293                 }
294                 handlers.fireEvent(new DataModelChangeEvent(action, DataModel.this, path));
295             }
296         });
297         handlers.fireEvent(new DataModelChangeEvent(Action.RELOAD, this, new QueryPath()));
298     }
299 
300     @Override
301     public HandlerRegistration addModelChangeHandler(ModelChangeHandler handler) {
302         return handlers.addHandler(ModelChangeEvent.TYPE, handler);
303     }
304 
305     public ModelDefinition getDefinition() {
306         return definition;
307     }
308 
309     public void setDefinition(ModelDefinition definition) {
310         this.definition = definition;
311     }
312 
313 
314     public String getParentPath() {
315         return parentPath;
316     }
317 
318 
319     /**
320      * If the root element for this is a child of another data object, then the parent
321      * path must be set to the path where this child data object can be found.
322      *
323      * @param parentPath
324      */
325     public void setParentPath(String parentPath) {
326         this.parentPath = parentPath;
327     }
328 
329     /**
330      * Validates this data model against its ModelDefinition/Metadata and returns the result
331      * to the callback
332      * @param callback
333      */
334     public void validate(final Callback<List<ValidationResultInfo>> callback) {
335         List<ValidationResultInfo> result = validator.validate(this);
336         callback.exec(result);
337     }
338 
339     /**
340      * Validates this data model against the next state in its ModelDefinition and returns the result
341      * to the callback
342      * @param callback
343      */
344     public void validateNextState(final Callback<List<ValidationResultInfo>> callback) {
345         List<ValidationResultInfo> result = validator.validateNextState(this);  // loads missingField result info 
346         callback.exec(result);
347     }
348 
349     /**
350      * Validates this data model against the given metadata and returns the result
351      * to the callback
352      * @param metadata
353      * @param callback
354      */
355     public void validateForMetadata(Metadata metadata, final Callback<List<ValidationResultInfo>> callback) {
356         List<ValidationResultInfo> result = validator.validateForMetadata(metadata, this);
357         callback.exec(result);
358     }
359     
360     /**
361      * Validates a single field
362      * @param fd
363      * @param callback
364      */
365     public void validateField(FieldDescriptor fd, final Callback<List<ValidationResultInfo>> callback) {
366         List<ValidationResultInfo> result = validator.validate(fd, this);
367         callback.exec(result);
368     }
369 
370     /**
371      * Checks to see if data exists for the path passed in
372      * @param sPath
373      * @return
374      */
375     public boolean isValidPath(String sPath) {
376         QueryPath path = QueryPath.parse(sPath);
377         boolean result = false;
378         Data root = this.getRoot();
379         for (int i = 0; i < path.size(); i++) {
380             Data.Key key = path.get(i);
381             if (!root.containsKey(key)) {
382                 result = false;
383                 break;
384             } else if (i < path.size() - 1) {
385                 root = (Data) root.get(key);
386             } else {
387                 result = true;
388                 break;
389             }
390         }
391         return result;
392     }
393 
394 }