1 /*
2 * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3 * the spring framework, http://el4j.sf.net
4 * Copyright (C) 2008 by ELCA Informatique SA, Av. de la Harpe 22-24,
5 * 1000 Lausanne, Switzerland, http://www.elca.ch
6 *
7 * EL4J is published under the GNU Lesser General Public License (LGPL)
8 * Version 2.1. See http://www.gnu.org/licenses/
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * For alternative licensing, please contact info@elca.ch
16 */
17 package ch.elca.el4j.services.persistence.hibernate.dao.extent;
18
19 import java.io.Serializable;
20
21 import static ch.elca.el4j.services.persistence.hibernate.dao.extent.ExtentEntity.rootEntity;
22
23
24 /**
25 * A DataExtent represents the extent of the graphs of objects to be
26 * loaded in a hibernate query.<br>
27 * Be careful in not mixing up the notion of an extent:<br>
28 * In this context it is referring to the extent of the DOM to be loaded from the underlying
29 * persistent store.<br>
30 * In other contexts like for example OpenJPA the notion can have a different meaning.<br>
31 * <br>
32 * <b>Principle:</b><br>
33 * An Extent represents a part of the DOM that should be loaded together.<br>
34 * It can be used to pool together associated entities in order
35 * to provide performance improvements over standard data fetching.<br>
36 * Specifying the extent when loading entities with Hibernate
37 * allows for tuning of lazy loading and eager fetching behavior.<br>
38 * For details about how to use the "Fetch Type" in order to control whether a field is fetched eagerly or lazily,
39 * see the corresponding reference manual of Java Persistence API
40 * (eg. {@link http://java.sun.com/javaee/5/docs/api/javax/persistence/FetchType.html})<br>
41 *
42 * In a DataExtent we internally distinguish between fields, entities and collections:<br>
43 * <ul>
44 * <li> Fields: fields/methods in persistent class which are of a simple java type or of type string or enum.<br>
45 * These fields are normally stored in the same table as the root entity and cannot have any child entities.<br>
46 * Note that you cannot lazy-load such fields without byte-code instrumentation, thus normally all fields are
47 * loaded eagerly and you should not have to mention any fields in your extent.<br>
48 * Exception: the type {@link java.sql.Blob} and {@link java.sql.Clob} are loaded lazy per default.
49 * Since there are different handling policies from different db vendors, be careful when using these types.<br>
50 * For example oracle takes care of the lazy loading of a blob outside a session, whereas with derby you have
51 * to read out the blob during the session into for example a byte array. For an example see
52 * {@link ch.elca.el4j.apps.refdb.dom.File}.
53 * <li> Entities: entities are the complex data types in a persistent class. Normally they get stored in a different
54 * entity table and can have child entities and fields themselves. To define an entity lazy loading, specify the
55 * fetch property for example in your association annotation:<br>
56 * <code>@OneToOne(fetch = FetchType.LAZY)</code><br>
57 * <li> Collections: all data types implementing the {@link java.util.Collection} interface should be
58 * added to the extent as collection for a proper fetching at runtime.
59 * </ul>
60 * <b>Remark:</b> When using DataExtent you don't have to have any knowledge
61 * about the db mapping or anything like that.
62 * It suffices to know the interface of the entity you are about to use. For example if you have:<br>
63 * <code><pre>
64 * public class Employee {
65 * ...
66 * public Employee getManager() {...
67 * </pre></code>
68 * To be sure that you can access the manager of an employee after retrieving it from the dao, add the manager to
69 * the extent you pass as an extra argument to the dao: <code>dao.findById(id, extent.with("manager"));</code><br>
70 * <br>
71 * Note that any part of the extent that is eagerly loaded according to the JPA metadata rules cannot be changed to
72 * a lazy loading behavior with DataExtent. On the other hand, all as lazy loading indicated parts can be forced to
73 * be loaded at runtime in each query.<br>
74 * Also, parts of the extent that does not get loaded but accessed at runtime will throw a LazyLoadingException as it
75 * would without using DataExtent's.<br>
76 * <br>
77 * <b>Features:</b> <br>
78 * <ul>
79 * <li> new DataExtent: provide root class and optionally the name of the root
80 * <li> with/without: add/remove fields, sub-entities and/or collections to/from the extent.<br>
81 * If the part to add already exists in the extent, the two corresponding parts are merged.
82 * <li> withSubentities: add sub-entities to the extent, convenient for adding entities you want
83 * to define in detail.
84 * <li> all: add everything to the graph of objects.
85 * <li> merge: merge two DataExtents to one. The class has to be the same.
86 * <li> freeze: freeze an extent to prevent any changes to it afterwards.<br>
87 * Example: the predefined convenience extents in
88 * {@link ch.elca.el4j.apps.refdb.dao.impl.hibernate.HibernateFileDao} and in
89 * {@link ch.elca.el4j.tests.person.dao.impl.hibernate.HibernatePersonDao} are frozen.
90 * <li> see also features of {@link ExtentEntity} to write your code in a convenient way
91 * </ul>
92 *
93 * Remark: Be sure to import the static methods {@link ExtentEntity#entity} and
94 * {@link ExtentCollection#collection} to create easily new Entities and Collections.<br>
95 * <br>
96 * <b>Sample code:</b> <br>
97 * <code>
98 * <pre>
99 * // The Extent Object of type 'Person'
100 * extent = new DataExtent(Person.class);
101 * // Construct a complex graph:
102 * // Person has a List of Teeth, a Tooth has a 'Person' as owner,
103 * // the owner has a list of 'Person' as friends, the friends are again
104 * // the same 'Person'-entity as defined in the beginning.
105 * extent.withSubentities(
106 * collection("teeth",
107 * entity(Tooth.class)
108 * .with("owner")
109 * ),
110 * collection("friends", extent.getRootEntity())
111 * );
112 *
113 * // Extent of a File
114 * extent = new DataExtent(File.class);
115 * // Create a simple, light extent for an overview over the files
116 * extent.with("name", "lastModified", "fileSize", "mimeType");
117 * dao.getAll(extent);
118 * // Note: there will potentially be loaded more than you specify, if it is defined to be fetched eagerly
119 *
120 * // Another extent to see also the file content
121 * extent = new DataExtent(File.class);
122 * extent.with("name", "content", "lastModified", "fileSize", "mimeType");
123 * dao.findById(id, extent);
124 *
125 * // Extent in a reference DOM, where references are loaded lazily
126 * extent = new DataExtent(Publication.class);
127 * extent.all(3);
128 * dao.findByName(publicationName, extent);
129 * // Now you have loaded all referenced publications, books, papers, ... to a depth of 3
130 * // Eg. using the parent of the parent is now possible.
131 * </pre>
132 * </code>
133 *
134 * @svnLink $Revision: 3874 $;$Date: 2009-08-04 14:25:40 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/hibernate/src/main/java/ch/elca/el4j/services/persistence/hibernate/dao/extent/DataExtent.java $
135 *
136 * @see ch.elca.el4j.apps.refdb.dao.impl.hibernate.HibernateFileDao for an example
137 *
138 * @author Andreas Rueedlinger (ARR)
139 */
140 public class DataExtent implements Serializable {
141 /** Defines the default loading depth. */
142 public static final int DEFAULT_LOADING_DEPTH = 1;
143
144 /** The root entity. */
145 private ExtentEntity m_rootEntity;
146
147 /**
148 * Default Creator.
149 * @param c the class of the root entity.
150 */
151 public DataExtent(Class<?> c) {
152 m_rootEntity = rootEntity(c);
153 }
154
155 /**
156 * Root entity of the extent.
157 * @return the root entity of the extent.
158 */
159 public ExtentEntity getRootEntity() {
160 return m_rootEntity;
161 }
162
163 /**
164 * The id of the data extent.
165 * If two extents contain the same subparts,
166 * this id should be equal.
167 * @return the id of the extent.
168 */
169 public String getExtentId() {
170 return m_rootEntity.getId();
171 }
172
173 //****************** Fluent API **********************//
174
175 /**
176 * Extend the extent by the given fields.
177 * Fields are either simple properties, sub-entities or collections.
178 * @param fields fields to be added.
179 *
180 * @return the new DataExtent Object.
181 * @throws NoSuchMethodException
182 */
183 public DataExtent with(String... fields) throws NoSuchMethodException {
184 m_rootEntity.with(fields);
185 return this;
186 }
187
188 /**
189 * Extend the extent by the given sub-entities.
190 * @param entities entities to be added.
191 *
192 * @return the new DataExtent Object.
193 * @throws NoSuchMethodException
194 */
195 public DataExtent withSubentities(AbstractExtentPart... entities) throws NoSuchMethodException {
196 m_rootEntity.withSubentities(entities);
197 return this;
198 }
199
200 /**
201 * Exclude fields from the extent.
202 * Fields are either simple properties, sub-entities or collections.
203 * @param fields fields to be excluded.
204 *
205 * @return the new DataExtent Object.
206 */
207 public DataExtent without(String...fields) {
208 m_rootEntity.without(fields);
209 return this;
210 }
211
212 /**
213 * Include all fields, entities and collections of the class.
214 * Exploration depth is DEFAULT_LOADING_DEPTH.
215 *
216 * @return the new DataExtent Object.
217 */
218 public DataExtent all() {
219 m_rootEntity.all(DEFAULT_LOADING_DEPTH);
220 return this;
221 }
222
223 /**
224 * Include all fields, entities and collections of the class.
225 * @param depth Exploration depth
226 *
227 * @return the new DataExtent Object.
228 */
229 public DataExtent all(int depth) {
230 m_rootEntity.all(depth);
231 return this;
232 }
233
234 /**
235 * Merge two DataExtents. Returns
236 * The class of the two rootEntities should be the same.
237 * @param other the extent to be merged with.
238 * @return the union of the extents.
239 */
240 public DataExtent merge(DataExtent other) {
241 m_rootEntity.merge(other.m_rootEntity);
242 return this;
243 }
244
245 /**
246 * Freeze the extent, meaning that no further changes to it are possible.
247 * @return the frozen extent.
248 */
249 public DataExtent freeze() {
250 m_rootEntity.freeze();
251 return this;
252 }
253
254 /** {@inheritDoc} */
255 @Override
256 public int hashCode() {
257 return getExtentId().hashCode();
258 }
259
260 /** {@inheritDoc} */
261 @Override
262 public boolean equals(Object object) {
263 if (super.equals(object)) {
264 return true;
265 } else if (object instanceof DataExtent) {
266 return getExtentId().equals(((DataExtent) object).getExtentId());
267 } else {
268 return false;
269 }
270 }
271
272 /** {@inheritDoc} */
273 @Override
274 public String toString() {
275 return getExtentId();
276 }
277
278
279 }