Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(430)

Side by Side Diff: pkg/gcloud/lib/src/db/model_db_impl.dart

Issue 804973002: Add appengine/gcloud/mustache dependencies. (Closed) Base URL: git@github.com:dart-lang/pub-dartlang-dart.git@master
Patch Set: Added AUTHORS/LICENSE/PATENTS files Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « pkg/gcloud/lib/src/db/model_db.dart ('k') | pkg/gcloud/lib/src/db/models.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of gcloud.db;
6
7 /// An implementation of [ModelDB] based on model class annotations.
8 ///
9 /// The two constructors will scan loaded dart libraries for classes with a
10 /// [Kind] annotation.
11 ///
12 /// An example on how to write a model class is:
13 /// @Kind
14 /// class Person extends db.Model {
15 /// @StringProperty
16 /// String name;
17 ///
18 /// @IntProperty
19 /// int age;
20 ///
21 /// @DateTimeProperty
22 /// DateTime dateOfBirth;
23 /// }
24 ///
25 /// These classes must either extend [Model] or [ExpandoModel]. Furthermore
26 /// they must have an empty default constructor which can be used to construct
27 /// model objects when doing lookups/queries from datastore.
28 class ModelDBImpl implements ModelDB {
29 final Map<_ModelDescription, Map<String, Property>> _modelDesc2Properties = {} ;
30 final Map<String, _ModelDescription> _kind2ModelDesc = {};
31 final Map<_ModelDescription, mirrors.ClassMirror> _modelDesc2ClassMirror = {};
32 final Map<_ModelDescription, Type> _type2ModelDesc = {};
33 final Map<Type, _ModelDescription> _modelDesc2Type = {};
34
35 /// Initializes a new [ModelDB] from all libraries.
36 ///
37 /// This will scan all libraries for classes with a [Kind] annotation.
38 ///
39 /// In case an error is encountered (e.g. two model classes with the same kind
40 /// name) a [StateError] will be thrown.
41 ModelDBImpl() {
42 // WARNING: This is O(n) of the source code, which is very bad!
43 // Would be nice to have: `currentMirrorSystem().subclassesOf(Model)`
44 _initialize(mirrors.currentMirrorSystem().libraries.values);
45 }
46
47 /// Initializes a new [ModelDB] from all libraries.
48 ///
49 /// This will scan the given [librarySymnbol] for classes with a [Kind]
50 /// annotation.
51 ///
52 /// In case an error is encountered (e.g. two model classes with the same kind
53 /// name) a [StateError] will be thrown.
54 ModelDBImpl.fromLibrary(Symbol librarySymbol) {
55 _initialize([mirrors.currentMirrorSystem().findLibrary(librarySymbol)]);
56 }
57
58 /// Converts a [datastore.Key] to a [Key].
59 Key fromDatastoreKey(datastore.Key datastoreKey) {
60 var namespace = new Partition(datastoreKey.partition.namespace);
61 Key key = namespace.emptyKey;
62 for (var element in datastoreKey.elements) {
63 var type = _type2ModelDesc[_kind2ModelDesc[element.kind]];
64 assert (type != null);
65 key = key.append(type, id: element.id);
66 }
67 return key;
68 }
69
70 /// Converts a [Key] to a [datastore.Key].
71 datastore.Key toDatastoreKey(Key dbKey) {
72 List<datastore.KeyElement> elements = [];
73 var currentKey = dbKey;
74 while (!currentKey.isEmpty) {
75 var id = currentKey.id;
76
77 var modelDescription = _modelDescriptionForType(currentKey.type);
78 var kind = modelDescription.kindName(this);
79
80 bool useIntegerId = modelDescription.useIntegerId;
81
82 if (useIntegerId && id != null && id is! int) {
83 throw new ArgumentError('Expected an integer id property but '
84 'id was of type ${id.runtimeType}');
85 }
86 if (!useIntegerId && (id != null && id is! String)) {
87 throw new ArgumentError('Expected a string id property but '
88 'id was of type ${id.runtimeType}');
89 }
90
91 elements.add(new datastore.KeyElement(kind, id));
92 currentKey = currentKey.parent;
93 }
94 Partition partition = currentKey._parent;
95 return new datastore.Key(
96 elements.reversed.toList(),
97 partition: new datastore.Partition(partition.namespace));
98 }
99
100 /// Converts a [Model] instance to a [datastore.Entity].
101 datastore.Entity toDatastoreEntity(Model model) {
102 try {
103 var modelDescription = _modelDescriptionForType(model.runtimeType);
104 return modelDescription.encodeModel(this, model);
105 } catch (error, stack) {
106 throw
107 new ArgumentError('Error while encoding entity ($error, $stack).');
108 }
109 }
110
111 /// Converts a [datastore.Entity] to a [Model] instance.
112 Model fromDatastoreEntity(datastore.Entity entity) {
113 if (entity == null) return null;
114
115 Key key = fromDatastoreKey(entity.key);
116 var kind = entity.key.elements.last.kind;
117 var modelDescription = _kind2ModelDesc[kind];
118 if (modelDescription == null) {
119 throw new StateError('Trying to deserialize entity of kind '
120 '$kind, but no Model class available for it.');
121 }
122
123 try {
124 return modelDescription.decodeEntity(this, key, entity);
125 } catch (error, stack) {
126 throw new StateError('Error while decoding entity ($error, $stack).');
127 }
128 }
129
130 /// Returns the string representation of the kind of model class [type].
131 ///
132 /// If the model class `type` is not found it will throw an `ArgumentError`.
133 String kindName(Type type) {
134 var kind = _modelDesc2Type[type].kind;
135 if (kind == null) {
136 throw new ArgumentError(
137 'The class $type was not associated with a kind.');
138 }
139 return kind;
140 }
141
142 /// Returns the name of the property corresponding to the kind [kind] and
143 /// [fieldName].
144 String fieldNameToPropertyName(String kind, String fieldName) {
145 var modelDescription = _kind2ModelDesc[kind];
146 if (modelDescription == null) {
147 throw new ArgumentError('The kind $kind is unknown.');
148 }
149 return modelDescription.fieldNameToPropertyName(fieldName);
150 }
151
152
153 Iterable<_ModelDescription> get _modelDescriptions {
154 return _modelDesc2Type.values;
155 }
156
157 Map<String, Property> _propertiesForModel(
158 _ModelDescription modelDescription) {
159 return _modelDesc2Properties[modelDescription];
160 }
161
162 _ModelDescription _modelDescriptionForType(Type type) {
163 return _modelDesc2Type[type];
164 }
165
166 mirrors.ClassMirror _modelClass(_ModelDescription md) {
167 return _modelDesc2ClassMirror[md];
168 }
169
170
171 void _initialize(Iterable<mirrors.LibraryMirror> libraries) {
172 libraries.forEach((mirrors.LibraryMirror lm) {
173 lm.declarations.values
174 .where((d) => d is mirrors.ClassMirror && d.hasReflectedType)
175 .forEach((mirrors.ClassMirror declaration) {
176 _tryLoadNewModelClass(declaration);
177 });
178 });
179
180 // Ask every [ModelDescription] to compute whatever global state it wants
181 // to have.
182 for (var modelDescription in _modelDescriptions) {
183 modelDescription.initialize(this);
184 }
185
186 // Ask every [ModelDescription] whether we should register it with a given
187 // kind name.
188 for (var modelDescription in _modelDescriptions) {
189 var kindName = modelDescription.kindName(this);
190 if (_kind2ModelDesc.containsKey(kindName)) {
191 throw new StateError(
192 'Cannot have two ModelDescriptions '
193 'with the same kind ($kindName)');
194 }
195 _kind2ModelDesc[kindName] = modelDescription;
196 }
197 }
198
199 void _tryLoadNewModelClass(mirrors.ClassMirror classMirror) {
200 Kind kindAnnotation;
201 for (mirrors.InstanceMirror instance in classMirror.metadata) {
202 if (instance.reflectee.runtimeType == Kind) {
203 if (kindAnnotation != null) {
204 throw new StateError(
205 'Cannot have more than one ModelMetadata() annotation '
206 'on a Model class');
207 }
208 kindAnnotation = instance.reflectee;
209 }
210 }
211
212 if (kindAnnotation != null) {
213 var name = kindAnnotation.name;
214 var integerId = kindAnnotation.idType == IdType.Integer;
215 var stringId = kindAnnotation.idType == IdType.String;
216
217 // Fall back to the class name.
218 if (name == null) {
219 name = mirrors.MirrorSystem.getName(classMirror.simpleName);
220 }
221
222 // This constraint should be guaranteed by the Kind() const constructor.
223 assert ((integerId && !stringId) || (!integerId && stringId));
224
225 _tryLoadNewModelClassFull(classMirror, name, integerId);
226 }
227 }
228
229 void _tryLoadNewModelClassFull(mirrors.ClassMirror modelClass,
230 String name,
231 bool useIntegerId) {
232 assert (!_modelDesc2Type.containsKey(modelClass.reflectedType));
233
234 var modelDesc;
235 if (_isExpandoClass(modelClass)) {
236 modelDesc = new _ExpandoModelDescription(name, useIntegerId);
237 } else {
238 modelDesc = new _ModelDescription(name, useIntegerId);
239 }
240
241 _type2ModelDesc[modelDesc] = modelClass.reflectedType;
242 _modelDesc2Type[modelClass.reflectedType] = modelDesc;
243 _modelDesc2ClassMirror[modelDesc] = modelClass;
244 _modelDesc2Properties[modelDesc] =
245 _propertiesFromModelDescription(modelClass);
246
247 // Ensure we have an empty constructor.
248 bool defaultConstructorFound = false;
249 for (var declaration in modelClass.declarations.values) {
250 if (declaration is mirrors.MethodMirror) {
251 if (declaration.isConstructor &&
252 declaration.constructorName == const Symbol('') &&
253 declaration.parameters.length == 0) {
254 defaultConstructorFound = true;
255 break;
256 }
257 }
258 }
259 if (!defaultConstructorFound) {
260 throw new StateError(
261 'Class ${modelClass.simpleName} does not have a default '
262 'constructor.');
263 }
264 }
265
266 Map<String, Property> _propertiesFromModelDescription(
267 mirrors.ClassMirror modelClassMirror) {
268 var properties = new Map<String, Property>();
269 var propertyNames = new Set<String>();
270
271 // Loop over all classes in the inheritence path up to the Object class.
272 while (modelClassMirror.superclass != null) {
273 var memberMap = modelClassMirror.instanceMembers;
274 // Loop over all declarations (which includes fields)
275 modelClassMirror.declarations.forEach((Symbol fieldSymbol,
276 mirrors.DeclarationMirror decl) {
277 // Look if the symbol is a getter and we have metadata attached to it.
278 if (memberMap.containsKey(fieldSymbol) &&
279 memberMap[fieldSymbol].isGetter &&
280 decl.metadata != null) {
281 var propertyAnnotations = decl.metadata
282 .map((mirrors.InstanceMirror mirror) => mirror.reflectee)
283 .where((Object property) => property is Property)
284 .toList();
285
286 if (propertyAnnotations.length > 1) {
287 throw new StateError(
288 'Cannot have more than one Property annotation on a model '
289 'field.');
290 } else if (propertyAnnotations.length == 1) {
291 var property = propertyAnnotations.first;
292
293 // Get a String representation of the field and the value.
294 var fieldName = mirrors.MirrorSystem.getName(fieldSymbol);
295
296 // Determine the name to use for the property in datastore.
297 var propertyName = (property as Property).propertyName;
298 if (propertyName == null) propertyName = fieldName;
299
300 if (properties.containsKey(fieldName)) {
301 throw new StateError(
302 'Cannot have two Property objects describing the same field '
303 'in a model object class hierarchy.');
304 }
305
306 if (propertyNames.contains(propertyName)) {
307 throw new StateError(
308 'Cannot have two Property objects mapping to the same '
309 'datastore property name "$propertyName".');
310 }
311 properties[fieldName] = property;
312 propertyNames.add(propertyName);
313 }
314 }
315 });
316 modelClassMirror = modelClassMirror.superclass;
317 }
318
319 return properties;
320 }
321
322 bool _isExpandoClass(mirrors.ClassMirror modelClass) {
323 while (modelClass.superclass != modelClass) {
324 if (modelClass.reflectedType == ExpandoModel) {
325 return true;
326 } else if (modelClass.reflectedType == Model) {
327 return false;
328 }
329 modelClass = modelClass.superclass;
330 }
331 throw new StateError('This should be unreachable.');
332 }
333 }
334
335 class _ModelDescription {
336 final HashMap<String, String> _property2FieldName =
337 new HashMap<String, String>();
338 final HashMap<String, String> _field2PropertyName =
339 new HashMap<String, String>();
340 final Set<String> _indexedProperties = new Set<String>();
341 final Set<String> _unIndexedProperties = new Set<String>();
342
343 final String kind;
344 final bool useIntegerId;
345
346 _ModelDescription(this.kind, this.useIntegerId);
347
348 void initialize(ModelDBImpl db) {
349 // Compute propertyName -> fieldName mapping.
350 db._propertiesForModel(this).forEach((String fieldName, Property prop) {
351 // The default of a datastore property name is the fieldName.
352 // It can be overridden with [Property.propertyName].
353 String propertyName = prop.propertyName;
354 if (propertyName == null) propertyName = fieldName;
355
356 _property2FieldName[propertyName] = fieldName;
357 _field2PropertyName[fieldName] = propertyName;
358 });
359
360 // Compute properties & unindexed properties
361 db._propertiesForModel(this).forEach((String fieldName, Property prop) {
362 String propertyName = prop.propertyName;
363 if (propertyName == null) propertyName = fieldName;
364
365 if (prop.indexed) {
366 _indexedProperties.add(propertyName);
367 } else {
368 _unIndexedProperties.add(propertyName);
369 }
370 });
371 }
372
373 String kindName(ModelDBImpl db) => kind;
374
375 datastore.Entity encodeModel(ModelDBImpl db, Model model) {
376 var key = db.toDatastoreKey(model.key);
377
378 var properties = {};
379 var mirror = mirrors.reflect(model);
380
381 db._propertiesForModel(this).forEach((String fieldName, Property prop) {
382 _encodeProperty(db, model, mirror, properties, fieldName, prop);
383 });
384
385 return new datastore.Entity(
386 key, properties, unIndexedProperties: _unIndexedProperties);
387 }
388
389 _encodeProperty(ModelDBImpl db, Model model, mirrors.InstanceMirror mirror,
390 Map properties, String fieldName, Property prop) {
391 String propertyName = prop.propertyName;
392 if (propertyName == null) propertyName = fieldName;
393
394 var value = mirror.getField(
395 mirrors.MirrorSystem.getSymbol(fieldName)).reflectee;
396 if (!prop.validate(db, value)) {
397 throw new StateError('Property validation failed for '
398 'property $fieldName while trying to serialize entity of kind '
399 '${model.runtimeType}. ');
400 }
401 properties[propertyName] = prop.encodeValue(db, value);
402 }
403
404 Model decodeEntity(ModelDBImpl db, Key key, datastore.Entity entity) {
405 if (entity == null) return null;
406
407 // NOTE: this assumes a default constructor for the model classes!
408 var classMirror = db._modelClass(this);
409 var mirror = classMirror.newInstance(const Symbol(''), []);
410
411 // Set the id and the parent key
412 mirror.reflectee.id = key.id;
413 mirror.reflectee.parentKey = key.parent;
414
415 db._propertiesForModel(this).forEach((String fieldName, Property prop) {
416 _decodeProperty(db, entity, mirror, fieldName, prop);
417 });
418 return mirror.reflectee;
419 }
420
421 _decodeProperty(ModelDBImpl db, datastore.Entity entity,
422 mirrors.InstanceMirror mirror, String fieldName,
423 Property prop) {
424 String propertyName = fieldNameToPropertyName(fieldName);
425
426 var rawValue = entity.properties[propertyName];
427 var value = prop.decodePrimitiveValue(db, rawValue);
428
429 if (!prop.validate(db, value)) {
430 throw new StateError('Property validation failed while '
431 'trying to deserialize entity of kind '
432 '${entity.key.elements.last.kind} (property name: $prop)');
433 }
434
435 mirror.setField(mirrors.MirrorSystem.getSymbol(fieldName), value);
436 }
437
438 String fieldNameToPropertyName(String fieldName) {
439 return _field2PropertyName[fieldName];
440 }
441
442 String propertyNameToFieldName(ModelDBImpl db, String propertySearchName) {
443 return _property2FieldName[propertySearchName];
444 }
445
446 Object encodeField(ModelDBImpl db, String fieldName, Object value) {
447 Property property = db._propertiesForModel(this)[fieldName];
448 if (property != null) return property.encodeValue(db, value);
449 return null;
450 }
451 }
452
453 // NOTE/TODO:
454 // Currently expanded properties are only
455 // * decoded if there are no clashes in [usedNames]
456 // * encoded if there are no clashes in [usedNames]
457 // We might want to throw an error if there are clashes, because otherwise
458 // - we may end up removing properties after a read-write cycle
459 // - we may end up dropping added properties in a write
460 // ([usedNames] := [realFieldNames] + [realPropertyNames])
461 class _ExpandoModelDescription extends _ModelDescription {
462 Set<String> realFieldNames;
463 Set<String> realPropertyNames;
464 Set<String> usedNames;
465
466 _ExpandoModelDescription(String kind, bool useIntegerId)
467 : super(kind, useIntegerId);
468
469 void initialize(ModelDBImpl db) {
470 super.initialize(db);
471
472 realFieldNames = new Set<String>.from(_field2PropertyName.keys);
473 realPropertyNames = new Set<String>.from(_property2FieldName.keys);
474 usedNames = new Set()..addAll(realFieldNames)..addAll(realPropertyNames);
475 }
476
477 datastore.Entity encodeModel(ModelDBImpl db, ExpandoModel model) {
478 var entity = super.encodeModel(db, model);
479 var properties = entity.properties;
480 model.additionalProperties.forEach((String key, Object value) {
481 // NOTE: All expanded properties will be indexed.
482 if (!usedNames.contains(key)) {
483 properties[key] = value;
484 }
485 });
486 return entity;
487 }
488
489 Model decodeEntity(ModelDBImpl db, Key key, datastore.Entity entity) {
490 if (entity == null) return null;
491
492 ExpandoModel model = super.decodeEntity(db, key, entity);
493 var properties = entity.properties;
494 properties.forEach((String key, Object value) {
495 if (!usedNames.contains(key)) {
496 model.additionalProperties[key] = value;
497 }
498 });
499 return model;
500 }
501
502 String fieldNameToPropertyName(String fieldName) {
503 String propertyName = super.fieldNameToPropertyName(fieldName);
504 // If the ModelDescription doesn't know about [fieldName], it's an
505 // expanded property, where propertyName == fieldName.
506 if (propertyName == null) propertyName = fieldName;
507 return propertyName;
508 }
509
510 String propertyNameToFieldName(ModelDBImpl db, String propertyName) {
511 String fieldName = super.propertyNameToFieldName(db, propertyName);
512 // If the ModelDescription doesn't know about [propertyName], it's an
513 // expanded property, where propertyName == fieldName.
514 if (fieldName == null) fieldName = propertyName;
515 return fieldName;
516 }
517
518 Object encodeField(ModelDBImpl db, String fieldName, Object value) {
519 Object primitiveValue = super.encodeField(db, fieldName, value);
520 // If superclass can't encode field, we return value here (and assume
521 // it's primitive)
522 // NOTE: Implicit assumption:
523 // If value != null then superclass will return != null.
524 // TODO: Ensure [value] is primitive in this case.
525 if (primitiveValue == null) primitiveValue = value;
526 return primitiveValue;
527 }
528 }
OLDNEW
« no previous file with comments | « pkg/gcloud/lib/src/db/model_db.dart ('k') | pkg/gcloud/lib/src/db/models.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698