/**
* Copyright (c) 2011, Thilo Planz. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package v7cr.v7db;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.bson.BasicBSONObject;
public class SchemaDefinition {
private final BSONBackedObject bson;
/**
* name of the field that defines the possible data types for the object.
*/
public static final String DATA_TYPE = "dataType";
/**
* name of the field that defines the fields for the object
*/
public static final String FIELDS = "fields";
/**
* name of the field that defines the cardinality
*/
public static final String CARDINALITY = "cardinality";
/**
* name of the field that holds the required flag
*/
public static final String REQUIRED = "required";
/**
* name of the field that holds the possible values
*/
public static final String POSSIBLE_VALUES = "possibleValues";
private final BSONBackedObject fields;
// to scope the type definitions
private final SchemaDefinition parent;
public SchemaDefinition(BSONBackedObject bson) {
this(bson, null);
}
private SchemaDefinition(BSONBackedObject bson, SchemaDefinition parent) {
this.bson = bson;
this.parent = parent;
BSONBackedObject f = bson.getObjectField(FIELDS);
if (f == null) {
// support field definitions in a nested dataType
// but only if there is just one option
String[] dt = getDataTypes();
if (dt.length == 1 && dt[0].startsWith(":")) {
SchemaDefinition type = getType(dt[0].substring(1));
if (type == null) {
System.err.println("unresolved nested dataType " + dt[0]);
} else {
f = type.fields;
}
}
}
fields = f;
}
SchemaDefinition(BasicBSONObject bson) {
this(BSONBackedObjectLoader.wrap(bson, null));
}
/**
* returns an empty set if there are no field definitions
*
* @return the names of fields for which definitions exist
*/
public Set<String> getFieldNames() {
if (fields == null)
return Collections.emptySet();
return fields.getFieldNames();
}
/**
* The caption should be used in user interface elements (such as form input
* labels or table headers) to name the field
*
* @return the caption for the named field (or null, if unspecified)
*/
public LocalizedString getFieldCaption(String fieldName) {
SchemaDefinition sd = getFieldDefinition(fieldName);
if (sd == null)
return null;
return LocalizedString.get(sd.bson, "caption");
}
/**
* The caption should be used in user interface elements (such as form input
* labels or table headers) to name the field
*
* @return the caption for the named field (or null, if unspecified)
*/
public String getFieldCaption(String fieldName, Locale l) {
SchemaDefinition sd = getFieldDefinition(fieldName);
if (sd == null)
return null;
return LocalizedString.get(sd.bson, "caption", l);
}
/**
* Returns the definition for the named field.
*/
public SchemaDefinition getFieldDefinition(String fieldName) {
if (fields == null)
return null;
int idx = fieldName.indexOf('.');
if (idx > -1) {
String first = fieldName.substring(0, idx);
SchemaDefinition d = getFieldDefinition(first);
if (d == null)
return null;
return d.getFieldDefinition(fieldName.substring(idx + 1));
}
BSONBackedObject field = fields.getObjectField(fieldName);
if (field == null)
return null;
return new SchemaDefinition(field, this);
}
/**
* Returns the allowable data types. If unspecified, returns the implied
* <code>{ "object" }</code>.
*
* @return an array of allowable data types, usually just one
*/
public String[] getDataTypes() {
String[] d = bson.getStringFieldAsArray(DATA_TYPE);
if (d == null) {
return new String[] { "object" };
}
return d;
}
/**
* Returns the possible values for this object. If unrestricted returns
* null.
* <p>
* This returns just the actual possible values, not any other meta-data the
* schema may contain. For the meta-data, use getPossibleValueMetaData
*/
public Object[] getPossibleValues() {
Object p = bson.getField(POSSIBLE_VALUES);
if (p == null)
return null;
if (!(p instanceof Object[])) {
p = new Object[] { p };
}
Object[] o = (Object[]) p;
int i = 0;
for (Object x : o) {
if (x instanceof BSONBackedObject) {
o[i] = ((BSONBackedObject) x).getField("_id");
}
i++;
}
return o;
}
/**
* Returns the meta-data associated with a possible value defined for this
* object. If values are unrestricted, returns null. If the value is not
* possible, returns null. If there is no meta-data, returns `{ _id :
* theValue } `.
*/
public BSONBackedObject getPossibleValueMetaData(Object value) {
Object p = bson.getField(POSSIBLE_VALUES);
if (p == null)
return null;
if (!(p instanceof Object[])) {
p = new Object[] { p };
}
Object[] o = (Object[]) p;
for (Object x : o) {
if (x instanceof BSONBackedObject) {
BSONBackedObject b = ((BSONBackedObject) x);
Object id = b.getField("_id");
if (value.equals(id))
return b;
continue;
}
if (value.equals(x))
return BSONBackedObjectLoader.wrap(
new BasicBSONObject("_id", x), null);
}
return null;
}
/**
* Returns the cardinality for this object. If unspecified, returns the
* implied "1"
*/
public Cardinality getCardinality() {
Cardinality c = Cardinality.getCardinality(bson
.getStringField(CARDINALITY));
if (c == null)
return Cardinality.ONE;
return c;
}
SchemaDefinition withCardinalityN() {
return new SchemaDefinition(bson.append(CARDINALITY, Cardinality.N
.toString()), parent);
}
SchemaDefinition withCardinality1() {
return new SchemaDefinition(bson.append(CARDINALITY, Cardinality.ONE
.toString()), parent);
}
public boolean isRequired() {
return Boolean.TRUE.equals(bson.getBooleanField("required"));
}
private Map<String, SchemaDefinition> localTypes;
/**
* Returns the SchemaDefinition for a complex type, as per the current scope
* (i.e. the types defined in this schema take precedence)
*
* @param name
* without the leading colon (:)
*/
public SchemaDefinition getType(String name) {
if (localTypes == null)
localTypes = new HashMap<String, SchemaDefinition>();
SchemaDefinition d = localTypes.get(name);
if (d != null)
return d;
BSONBackedObject t = bson.getObjectField("types");
if (t == null) {
if (parent != null)
return parent.getType(name);
return null;
}
BSONBackedObject s = t.getObjectField(name);
if (s == null) {
if (parent != null)
return parent.getType(name);
return null;
}
d = new SchemaDefinition(s, this);
localTypes.put(name, d);
return d;
}
public static SchemaDefinition parse(String json) {
return new SchemaDefinition(BSONBackedObjectLoader.parse(json, null));
}
}