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