/* * Copyright 2008 Alberto Gimeno <gimenete at gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package siena; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import siena.ClassInfo.FieldMapKeys; import siena.core.Aggregator; import siena.core.Many4PM; import siena.core.One; import siena.core.One4PM; import siena.core.Relation; import siena.core.RelationMode; import siena.core.SyncList; import siena.core.async.ModelAsync; import siena.core.async.QueryAsync; import siena.core.batch.Batch; import siena.core.options.QueryOption; import siena.core.options.QueryOptionState; /** * This is the base abstract class to extend your domain classes. * It's strongly recommended to implement the static method "all". Example: * * For example: * * <code> * public static Query<YourClass> all() { * return Model.all(YourClass.class); * } * </code> * * @author gimenete * @author mandubian * */ public abstract class Model { transient private PersistenceManager persistenceManager; // this is a technical field used by Siena to represent a relation // between this model and another one. For ex, it will be used to // identify the ancestor in an aggregation relation // has transient modifier to prevent serialization FOR THE TIME BEING // TODO IS THE TRANSIENT REQUIRED OR CAN IT BE A PB? @Aggregator private Relation relation; public Model() { init(); } public void get() { getPersistenceManager().get(this); } public void delete() { getPersistenceManager().delete(this); } public void insert() { getPersistenceManager().insert(this); } public void update() { getPersistenceManager().update(this); } public void save() { getPersistenceManager().save(this); } public final PersistenceManager getPersistenceManager() { if(persistenceManager == null) { persistenceManager = PersistenceManagerFactory.getPersistenceManager(getClass()); } return persistenceManager; } public static <R> Query<R> all(Class<R> clazz) { return PersistenceManagerFactory.getPersistenceManager(clazz).createQuery(clazz); } public static <R> Batch<R> batch(Class<R> clazz) { return PersistenceManagerFactory.getPersistenceManager(clazz).createBatch(clazz); } public static <R> R getByKey(Class<R> clazz, Object key) { return PersistenceManagerFactory.getPersistenceManager(clazz).getByKey(clazz, key); } public ModelAsync async() { return new ModelAsync(this); } public boolean hasRelation(RelationMode mode) { if(mode == RelationMode.AGGREGATION){ return Util.readField( this, ClassInfo.getClassInfo(this.getClass()).aggregator) != null; } return false; } public Model setRelation(Relation relation){ Util.setField(this, ClassInfo.getClassInfo(this.getClass()).aggregator, relation); return this; } public Relation getRelation(){ return (Relation)Util.readField(this, ClassInfo.getClassInfo(this.getClass()).aggregator); } public Model aggregate(Object aggregator, String fieldName){ return setRelation( new Relation(RelationMode.AGGREGATION, aggregator, Util.getField(aggregator.getClass(), fieldName))); } public boolean equals(Object that) { if(this == that) { return true; } if(that == null || that.getClass() != this.getClass()) { return false; } List<Field> keys = ClassInfo.getClassInfo(getClass()).keys; for (Field field : keys) { field.setAccessible(true); try { Object a = field.get(this); Object b = field.get(that); if(a == null ? b != null : !a.equals(b)) { return false; } } catch (Exception e) { throw new SienaException(e); } } return true; } public int hashCode() { final int prime = 31; int result = 1; List<Field> keys = ClassInfo.getClassInfo(getClass()).keys; for (Field field : keys) { field.setAccessible(true); try { Object value = field.get(this); result = prime * result + ((value == null) ? 0 : value.hashCode()); } catch (Exception e) { throw new SienaException(e); } } return result; } @SuppressWarnings({ "unchecked", "rawtypes" }) private void init() { // initialize Query<T> types Class<?> clazz = getClass(); // Takes into account superclass fields for inheritance!!!! ClassInfo info = ClassInfo.getClassInfo(clazz); for(Field field:info.queryFieldMap.keySet()){ try { Map<FieldMapKeys, Object> map = info.queryFieldMap.get(field); Util.setField(this, field, new ProxyQuery((Class<?>)map.get(FieldMapKeys.CLASS), (String)map.get(FieldMapKeys.FILTER), this)); } catch (Exception e) { throw new SienaException(e); } } for(Field field:info.manyFieldMap.keySet()){ try { Map<FieldMapKeys, Object> map = info.manyFieldMap.get(field); RelationMode mode = (RelationMode)map.get(FieldMapKeys.MODE); switch(mode){ case AGGREGATION: Util.setField(this, field, new ProxyMany((Class<?>)map.get(FieldMapKeys.CLASS), this, (RelationMode)map.get(FieldMapKeys.MODE), field)); break; case RELATION: Util.setField(this, field, new ProxyMany((Class<?>)map.get(FieldMapKeys.CLASS), this, (RelationMode)map.get(FieldMapKeys.MODE), (Field)map.get(FieldMapKeys.FIELD))); break; } } catch (Exception e) { throw new SienaException(e); } } for(Field field:info.oneFieldMap.keySet()){ try { Map<FieldMapKeys, Object> map = info.oneFieldMap.get(field); RelationMode mode = (RelationMode)map.get(FieldMapKeys.MODE); switch(mode){ case AGGREGATION: Util.setField(this, field, new ProxyOne((Class<?>)map.get(FieldMapKeys.CLASS), this, (RelationMode)map.get(FieldMapKeys.MODE), field)); break; case RELATION: Util.setField(this, field, new ProxyOne((Class<?>)map.get(FieldMapKeys.CLASS), this, (RelationMode)map.get(FieldMapKeys.MODE), (Field)map.get(FieldMapKeys.FIELD))); break; } } catch (Exception e) { throw new SienaException(e); } } } class ProxyQuery<T> implements Query<T> { private static final long serialVersionUID = -7726081283511624780L; private String filter; private Class<T> clazz; private Model obj; private Query<T> query; public ProxyQuery(Class<T> clazz, String filter, Model obj) { this.filter = filter; this.clazz = clazz; this.obj = obj; } private Query<T> createQuery() { //return getPersistenceManager().createQuery(clazz).filter(filter, obj); // initializes once the query and reuses it // it is not initialized in the constructor because the persistencemanager might not be // initialized correctly with the Model if(this.query == null){ this.query = obj.getPersistenceManager().createQuery(clazz); } else if(((QueryOptionState)this.query.option(QueryOptionState.ID)).isStateless()) this.query.resetData(); return this.query.filter(filter, obj); } public int count() { return createQuery().count(); } @Deprecated public int count(int limit) { return createQuery().count(limit); } @Deprecated public int count(int limit, Object offset) { return createQuery().count(limit, offset); } public List<T> fetch() { return createQuery().fetch(); } public List<T> fetch(int limit) { return createQuery().fetch(limit); } public List<T> fetch(int limit, Object offset) { return createQuery().fetch(limit, offset); } public Query<T> filter(String fieldName, Object value) { return createQuery().filter(fieldName, value); } public Query<T> order(String fieldName) { return createQuery().order(fieldName); } @Deprecated public Query<T> search(String match, boolean inBooleanMode, String index) { return createQuery().search(match, inBooleanMode, index); } public Query<T> join(String field, String... sortFields) { return createQuery().join(field, sortFields); } public Query<T> aggregated(Object aggregator, String fieldName) { return createQuery().aggregated(aggregator, fieldName); } public Query<T> owned(Object owner, String fieldName) { return createQuery().owned(owner, fieldName); } public T get() { return createQuery().get(); } public Iterable<T> iter() { return createQuery().iter(); } public Iterable<T> iter(int limit) { return createQuery().iter(limit); } public Iterable<T> iter(int limit, Object offset) { return createQuery().iter(limit, offset); } public Iterable<T> iterPerPage(int limit) { return createQuery().iterPerPage(limit); } public ProxyQuery<T> copy() { return new ProxyQuery<T>(clazz, filter, obj); } @Deprecated public Object nextOffset() { return createQuery().nextOffset(); } public int delete() { return createQuery().delete(); } public List<T> fetchKeys() { return createQuery().fetchKeys(); } public List<T> fetchKeys(int limit) { return createQuery().fetchKeys(limit); } public List<T> fetchKeys(int limit, Object offset) { return createQuery().fetchKeys(limit, offset); } public List<QueryFilter> getFilters() { return createQuery().getFilters(); } public List<QueryOrder> getOrders() { return createQuery().getOrders(); } public List<QueryFilterSearch> getSearches() { return createQuery().getSearches(); } public List<QueryJoin> getJoins() { return createQuery().getJoins(); } public List<QueryAggregated> getAggregatees() { return createQuery().getAggregatees(); } public List<QueryOwned> getOwnees() { return createQuery().getOwnees(); } @Deprecated public void setNextOffset(Object nextOffset) { createQuery().setNextOffset(nextOffset); } public Class<T> getQueriedClass() { return clazz; } public Query<T> paginate(int limit) { return createQuery().paginate(limit); } public Query<T> limit(int limit) { return createQuery().limit(limit); } public Query<T> offset(Object offset) { return createQuery().offset(offset); } public Query<T> customize(QueryOption... options) { return createQuery().customize(options); } public QueryOption option(int option) { return createQuery().option(option); } public Map<Integer, QueryOption> options() { return createQuery().options(); } public Query<T> stateful() { return createQuery().stateful(); } public Query<T> stateless() { return createQuery().stateless(); } public Query<T> release() { return createQuery().release(); } public Query<T> resetData() { return createQuery().resetData(); } public Query<T> search(String match, String... fields) { return createQuery().search(match, fields); } public Query<T> search(String match, QueryOption opt, String... fields) { return createQuery().search(match, opt, fields); } public int update(Map<String, ?> fieldValues) { return createQuery().update(fieldValues); } public Query<T> nextPage() { return createQuery().nextPage(); } public Query<T> previousPage() { return createQuery().previousPage(); } public String dump() { return createQuery().dump(); } public Query<T> restore(String dump) { return createQuery().restore(dump); } public QueryAsync<T> async() { return createQuery().async(); } public T getByKey(Object key) { return createQuery().getByKey(key); } public PersistenceManager getPersistenceManager() { return obj.getPersistenceManager(); } public String dump(QueryOption... options) { return createQuery().dump(options); } public void dump(OutputStream os, QueryOption... options) { createQuery().dump(os, options); } public Query<T> restore(String dump, QueryOption... options) { return createQuery().restore(dump, options); } public Query<T> restore(InputStream dump, QueryOption... options) { return createQuery().restore(dump, options); } } class ProxyMany<T> implements Many4PM<T> { private static final long serialVersionUID = -4540064249546783019L; private Class<T> clazz; private Model obj; private Many4PM<T> many; private RelationMode mode; private Field field; public ProxyMany(Class<T> clazz, Model obj, RelationMode mode, Field field) { this.clazz = clazz; this.obj = obj; this.mode = mode; this.field = field; } private Many4PM<T> createMany() { if(this.many == null){ this.many = obj.getPersistenceManager().createMany(clazz); } //else if(((QueryOptionState)this.listQuery.asQuery().option(QueryOptionState.ID)).isStateless()){ // this.listQuery.asQuery().release(); //} switch(mode){ case AGGREGATION: aggregationMode(obj, field); break; case RELATION: relationMode(obj, field); break; } return this.many; } public SyncList<T> asList() { return createMany().asList(); } public Query<T> asQuery() { return createMany().asQuery(); } public List<T> asList2Remove() { return ((Many4PM<T>)createMany()).asList2Remove(); } public List<T> asList2Add() { return ((Many4PM<T>)createMany()).asList2Add(); } public Many4PM<T> aggregationMode(Object aggregator, Field field) { return this.many.aggregationMode(aggregator, field); } public Many4PM<T> relationMode(Object owner, Field field) { return this.many.relationMode(owner, field); } public Many4PM<T> setSync(boolean isSync) { return createMany().setSync(isSync); } } class ProxyOne<T> implements One4PM<T> { private Class<T> clazz; private Model ancestor; private One4PM<T> one; private RelationMode mode; private Field field; public ProxyOne(Class<T> clazz, Model ancestor, RelationMode mode, Field field) { this.clazz = clazz; this.ancestor = ancestor; this.mode = mode; this.field = field; } private One4PM<T> createOne() { if(this.one == null){ this.one = ancestor.getPersistenceManager().createOne(clazz); switch(mode){ case AGGREGATION: aggregationMode(ancestor, field); break; case RELATION: relationMode(ancestor, field); break; } } return this.one; } public One4PM<T> aggregationMode(Object aggregator, Field field) { return this.one.aggregationMode(aggregator, field); } public One4PM<T> relationMode(Object owner, Field field) { return this.one.relationMode(owner, field); } public T get() { return createOne().get(); } public void set(T obj) { createOne().set(obj); } public One<T> sync() { return createOne().sync(); } public One<T> forceSync() { return createOne().forceSync(); } public boolean isModified() { return createOne().isModified(); } public One4PM<T> setModified(boolean isModified) { return createOne().setModified(isModified); } public T getPrev() { return createOne().getPrev(); } public One4PM<T> setSync(boolean isSync) { return createOne().setSync(isSync); } } }