All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.vaadin.JObjectContainer Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.vaadin;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.SortedMap;

import org.dellroad.stuff.vaadin7.PropertyDef;
import org.dellroad.stuff.vaadin7.PropertyExtractor;
import org.dellroad.stuff.vaadin7.ProvidesPropertyScanner;
import org.dellroad.stuff.vaadin7.SimpleItem;
import org.dellroad.stuff.vaadin7.SimpleKeyedContainer;
import org.jsimpledb.CopyState;
import org.jsimpledb.JCollectionField;
import org.jsimpledb.JCounterField;
import org.jsimpledb.JField;
import org.jsimpledb.JFieldSwitchAdapter;
import org.jsimpledb.JMapField;
import org.jsimpledb.JObject;
import org.jsimpledb.JSimpleDB;
import org.jsimpledb.JSimpleField;
import org.jsimpledb.JTransaction;
import org.jsimpledb.ValidationMode;
import org.jsimpledb.core.DeletedObjectException;
import org.jsimpledb.core.ObjId;
import org.jsimpledb.core.UnknownFieldException;
import org.jsimpledb.core.util.ObjIdSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Vaadin {@link com.vaadin.data.Container} backed by {@link JSimpleDB} Java model objects.
 *
 * 

* Automatically creates container properties for object ID, database type, schema version, and all fields, as well as any custom * properties defined by {@link org.dellroad.stuff.vaadin7.ProvidesProperty @ProvidesProperty}-annotated methods in * Java model classes. The properties of each {@link com.vaadin.data.Item} in the container are derived from a corresponding * {@link JObject} which is usually stored in an in-memory {@link org.jsimpledb.SnapshotJTransaction} (which may contain other * related objects, allowing an {@link com.vaadin.data.Item}'s properties to be derived from those related objects as well). * *

* Instances are configured with a type, which can be any Java type. The container will then be restricted to * database objects that are instances of the configured type. The type may be null, in which case there is no restriction. * *

* Instances are loaded by invoking {@link #load load()} with an iteration of backing {@link JObject}s. * Normally these {@link JObject}s are contained in a {@link org.jsimpledb.SnapshotJTransaction}. * *

* Instances implement {@link org.dellroad.stuff.vaadin7.Connectable} and therefore must be {@link #connect connect()}'ed * prior to use and {@link #disconnect disconnect()}'ed after use (usually done in the associated widget's * {@link com.vaadin.ui.Component#attach attach()} and {@link com.vaadin.ui.Component#detach detach()} methods). * *

* Container Properties * *

* Instances have the following container properties: *

    *
  • {@link #OBJECT_ID_PROPERTY}: Object {@link ObjId}
  • *
  • {@link #TYPE_PROPERTY}: Object type name (JSimpleDB type name, not Java type name, though the former * is by default the simple Java type name)
  • *
  • {@link #VERSION_PROPERTY}: Object schema version
  • *
  • {@link #REFERENCE_LABEL_PROPERTY}: Object reference label, which is a short description identifying the * object. Reference labels are used to provide "names" for objects that are more meaningful than object ID's * and are used as such in other {@link JSimpleDB} GUI classes, for example when displaying the object in a list. * To customize the reference label for a Java model class, * annotate a method with {@link org.dellroad.stuff.vaadin7.ProvidesProperty @ProvidesProperty}{@code (}{@link * JObjectContainer#REFERENCE_LABEL_PROPERTY REFERENCE_LABEL_PROPERTY}{@code )}; * otherwise, the value of this property will be the same as {@link #OBJECT_ID_PROPERTY}. Note that objects with * customized reference labels will need to be included in the snapshot transaction also if they are referenced * by an object actually in the container. *
  • *
  • A property for every {@link JSimpleDB} field that is common to all object types that sub-type * this containers's configured type. The property's ID is the field name; its value is as follows: *
      *
    • For simple fields, their {@linkplain org.jsimpledb.core.FieldType#toString(Object) string form}
    • *
    • For reference fields, the {@link #REFERENCE_LABEL_PROPERTY} of the referred-to object, or "Null" * if the reference is null
    • *
    • For set, list, and map fields, the first few entries in the collection
    • *
    *
  • *
  • A property for each {@link org.dellroad.stuff.vaadin7.ProvidesProperty @ProvidesProperty}-annotated method * in the specified type. These properties will add to (or override) the properties listed above. *
*/ @SuppressWarnings("serial") public class JObjectContainer extends SimpleKeyedContainer { /** * Container property name for the reference label property, which has type {@link Component}. */ public static final String REFERENCE_LABEL_PROPERTY = "$label"; /** * Container property name for the object ID property. */ public static final String OBJECT_ID_PROPERTY = "$objId"; /** * Container property name for the object type property. */ public static final String TYPE_PROPERTY = "$type"; /** * Container property name for the object schema version property. */ public static final String VERSION_PROPERTY = "$version"; protected final Logger log = LoggerFactory.getLogger(this.getClass()); /** * The associated {@link JSimpleDB}. */ protected final JSimpleDB jdb; private final ObjIdPropertyDef objIdPropertyDef = new ObjIdPropertyDef(); private final ObjTypePropertyDef objTypePropertyDef = new ObjTypePropertyDef(); private final ObjVersionPropertyDef objVersionPropertyDef = new ObjVersionPropertyDef(); private final RefLabelPropertyDef refLabelPropertyDef = new RefLabelPropertyDef(); private Class type; private ProvidesPropertyScanner propertyScanner; private List orderedPropertyNames; /** * Constructor. * * @param jdb {@link JSimpleDB} database * @param type type restriction, or null for no restriction * @throws IllegalArgumentException if {@code jdb} is null */ protected JObjectContainer(JSimpleDB jdb, Class type) { Preconditions.checkArgument(jdb != null, "null jdb"); this.jdb = jdb; this.setType(type); this.setPropertyExtractor(this); } /** * Get the type restriction associated with this instance. * * @return Java type restriction, or null if there is none */ public Class getType() { return this.type; } /** * Change the type restriction associated with this instance. * Triggers a {@link com.vaadin.data.Container.PropertySetChangeEvent} and typically requires a reload. * * @param type Java type restriction, or null for none * @param Java type */ public void setType(Class type) { this.type = type; this.propertyScanner = this.type != null ? new ProvidesPropertyScanner(/*this.*/type) : null; final ArrayList> propertyDefs = new ArrayList<>(this.buildPropertyDefs()); this.orderedPropertyNames = Collections.unmodifiableList(Lists.transform(propertyDefs, PropertyDef::getName)); this.setProperties(propertyDefs); this.fireContainerPropertySetChange(); } /** * Get the properties of this container in preferred order. * * @return property names */ public List getOrderedPropertyNames() { return this.orderedPropertyNames; } @Override public ObjId getKeyFor(JObject jobj) { return jobj.getObjId(); } /** * Load this container using the supplied backing {@link JObject}s. * *

* A container {@link com.vaadin.data.Item} will be created wrapping each iterated {@link JObject}; * {@link com.vaadin.data.Item} properties are accessible only while the containing transaction remains open. * * @param jobjs backing {@link JObject}s */ @Override public void load(Iterable jobjs) { this.load(jobjs.iterator()); } /** * Load this container using the supplied backing {@link JObject}s. * *

* A container {@link com.vaadin.data.Item} will be created wrapping each iterated {@link JObject}; * {@link com.vaadin.data.Item} properties are accessible only while the containing transaction remains open. * * @param jobjs backing {@link JObject}s */ @Override public void load(Iterator jobjs) { // Filter out any instances of the wrong type if (this.type != null) jobjs = Iterators.filter(jobjs, this.type::isInstance); // Filter out nulls and duplicates final ObjIdSet seenIds = new ObjIdSet(); jobjs = Iterators.filter(jobjs, jobj -> jobj != null && seenIds.add(jobj.getObjId())); // Proceed super.load(jobjs); } /** * Update a single item in this container by updating its backing object. * *

* This updates the backing object with the same object ID as {@code jobj}, if any, and then fires * {@link com.vaadin.data.Property.ValueChangeEvent}s for all properties of the corresponding * {@link com.vaadin.data.Item}. * * @param jobj updated database object */ public void updateItem(JObject jobj) { Preconditions.checkArgument(jobj != null, "null jobj"); final SimpleItem item = (SimpleItem)this.getItem(jobj.getObjId()); if (item != null) { jobj.copyTo(item.getObject().getTransaction(), new CopyState()); item.fireValueChange(); } } /** * Perform the given action within a new {@link JTransaction}. * *

* The implementation in {@link JObjectContainer} performs {@code action} within a new read-only transaction. * Note that {@code action} should be idempotent because the transaction will be retried if needed. * * @param action the action to perform */ protected void doInTransaction(Runnable action) { final JTransaction jtx = this.jdb.createTransaction(false, ValidationMode.DISABLED); jtx.getTransaction().setReadOnly(true); try { jtx.performAction(action); } finally { jtx.commit(); } } // Property derivation private Collection> buildPropertyDefs() { final PropertyDefHolder pdefs = new PropertyDefHolder(); // Add properties shared by all JObjects pdefs.setPropertyDef(this.refLabelPropertyDef); pdefs.setPropertyDef(this.objIdPropertyDef); pdefs.setPropertyDef(this.objTypePropertyDef); pdefs.setPropertyDef(this.objVersionPropertyDef); // Add properties for all fields common to all sub-types of our configured type final SortedMap jfields = Util.getCommonJFields(this.jdb.getJClasses(this.type)); if (jfields != null) { for (JField jfield : jfields.values()) pdefs.setPropertyDef(new ObjFieldPropertyDef(jfield.getStorageId(), jfield.getName())); } // Apply any @ProvidesProperty-annotated method properties, possibly overridding jfields if (this.propertyScanner != null) { for (PropertyDef propertyDef : this.propertyScanner.getPropertyDefs()) pdefs.setPropertyDef(propertyDef); } // Done return pdefs.values(); } // PropertyExtractor @Override @SuppressWarnings("unchecked") public V getPropertyValue(JObject jobj, PropertyDef propertyDef) { if (propertyDef instanceof ObjPropertyDef) return (V)((ObjPropertyDef)propertyDef).extract(jobj); if (this.propertyScanner == null) throw new IllegalArgumentException("unknown property: " + propertyDef.getName()); return JObjectContainer.extractProperty(this.propertyScanner.getPropertyExtractor(), propertyDef, jobj); } @SuppressWarnings("unchecked") private static V extractProperty(PropertyExtractor propertyExtractor, PropertyDef propertyDef, JObject jobj) { try { return ((PropertyExtractor)propertyExtractor).getPropertyValue(jobj, propertyDef); } catch (DeletedObjectException e) { try { return propertyDef.getType().cast(new SizedLabel("Unavailable", ContentMode.HTML)); } catch (ClassCastException e2) { try { return propertyDef.getType().cast("(Unavailable)"); } catch (ClassCastException e3) { return null; } } } } // ObjPropertyDef /** * Support superclass for {@link PropertyDef} implementations that derive the property value from a {@link JObject}. */ public abstract static class ObjPropertyDef extends PropertyDef { protected ObjPropertyDef(String name, Class type) { super(name, type); } public abstract T extract(JObject jobj); } // ObjIdPropertyDef /** * Implements the {@link JObjectContainer#OBJECT_ID_PROPERTY} property. */ public static class ObjIdPropertyDef extends ObjPropertyDef { public ObjIdPropertyDef() { super(OBJECT_ID_PROPERTY, SizedLabel.class); } @Override public SizedLabel extract(JObject jobj) { return new SizedLabel("" + jobj.getObjId() + "", ContentMode.HTML); } } // ObjTypePropertyDef /** * Implements the {@link JObjectContainer#TYPE_PROPERTY} property. */ public static class ObjTypePropertyDef extends ObjPropertyDef { public ObjTypePropertyDef() { super(TYPE_PROPERTY, SizedLabel.class); } @Override public SizedLabel extract(JObject jobj) { return new SizedLabel(jobj.getTransaction().getTransaction().getSchemas() .getVersion(jobj.getSchemaVersion()).getObjType(jobj.getObjId().getStorageId()).getName()); } } // ObjVersionPropertyDef /** * Implements the {@link JObjectContainer#VERSION_PROPERTY} property. */ public static class ObjVersionPropertyDef extends ObjPropertyDef { public ObjVersionPropertyDef() { super(VERSION_PROPERTY, SizedLabel.class); } @Override public SizedLabel extract(JObject jobj) { return new SizedLabel("" + jobj.getSchemaVersion()); } } // RefLabelPropertyDef /** * Implements the {@link JObjectContainer#REFERENCE_LABEL_PROPERTY} property. */ public static class RefLabelPropertyDef extends ObjPropertyDef { public RefLabelPropertyDef() { super(REFERENCE_LABEL_PROPERTY, Component.class); } @Override public Component extract(JObject jobj) { final ReferenceMethodInfoCache.PropertyInfo propertyInfo = ReferenceMethodInfoCache.getInstance().getReferenceMethodInfo(jobj.getClass()); if (propertyInfo == ReferenceMethodInfoCache.NOT_FOUND) return new ObjIdPropertyDef().extract(jobj); final Object value = JObjectContainer.extractProperty( propertyInfo.getPropertyExtractor(), propertyInfo.getPropertyDef(), jobj); if (value instanceof Component) return (Component)value; return new SizedLabel(String.valueOf(value)); } } // ObjFieldPropertyDef /** * Implements a property reflecting the value of a {@link JSimpleDB} field. */ public class ObjFieldPropertyDef extends ObjPropertyDef { private static final int MAX_ITEMS = 3; private final int storageId; public ObjFieldPropertyDef(int storageId, String name) { super(name, Component.class); this.storageId = storageId; } @Override public Component extract(final JObject jobj) { final JField jfield = JObjectContainer.this.jdb.getJClass(jobj.getObjId()).getJField(this.storageId, JField.class); try { return jfield.visit(new JFieldSwitchAdapter() { @Override public Component caseJSimpleField(JSimpleField field) { return ObjFieldPropertyDef.this.handleValue(field.getValue(jobj)); } @Override public Component caseJCounterField(JCounterField field) { return ObjFieldPropertyDef.this.handleValue(field.getValue(jobj).get()); } @Override protected Component caseJCollectionField(JCollectionField field) { return ObjFieldPropertyDef.this.handleCollectionField(field.getValue(jobj)); } @Override public Component caseJMapField(JMapField field) { return ObjFieldPropertyDef.this.handleMultiple(Iterables.transform( field.getValue(jobj).entrySet(), entry -> { final HorizontalLayout layout = new HorizontalLayout(); layout.setMargin(false); layout.setSpacing(false); layout.addComponent(ObjFieldPropertyDef.this.handleValue(entry.getKey())); layout.addComponent(new SizedLabel(" \u21d2 ")); // RIGHTWARDS DOUBLE ARROW layout.addComponent(ObjFieldPropertyDef.this.handleValue(entry.getValue())); return layout; })); } }); } catch (UnknownFieldException e) { return new SizedLabel("NA", ContentMode.HTML); } } @Override public boolean equals(Object obj) { if (obj == this) return true; if (!super.equals(obj)) return false; final ObjFieldPropertyDef that = (ObjFieldPropertyDef)obj; return this.storageId == that.storageId; } @Override public int hashCode() { return super.hashCode() ^ this.storageId; } private Component handleCollectionField(Collection col) { return this.handleMultiple(Iterables.transform(col, this::handleValue)); } private Component handleMultiple(Iterable components) { final HorizontalLayout layout = new HorizontalLayout(); layout.setMargin(false); layout.setSpacing(false); int count = 0; for (Component component : components) { if (count >= MAX_ITEMS) { layout.addComponent(new SizedLabel("...")); break; } if (count > 0) layout.addComponent(new SizedLabel(", ", ContentMode.HTML)); layout.addComponent(component); count++; } return layout; } @SuppressWarnings("unchecked") private Component handleValue(Object value) { if (value == null) return new SizedLabel("Null", ContentMode.HTML); if (value instanceof JObject) return new RefLabelPropertyDef().extract((JObject)value); return new SizedLabel(String.valueOf(value)); } } // PropertyDefHolder private static class PropertyDefHolder extends LinkedHashMap> { public void setPropertyDef(PropertyDef propertyDef) { this.put(propertyDef.getName(), propertyDef); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy