View Javadoc

1   /*
2    * This file is a part of CAST project.
3    * (c) Copyright 2007, AGH University of Science & Technology
4    * https://caribou.iisg.agh.edu.pl/trac/cast
5    *
6    * Licensed under the Eclipse Public License, Version 1.0 (the "License").
7    * You may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * http://www.eclipse.org/legal/epl-v10.html
10   */
11  /*
12   * File: Node.java
13   * Created: 2007-09-18
14   * Author: apohllo
15   * $Id: Node.java 2232 2009-01-04 22:59:53Z apohllo $
16   */
17  
18  package pl.edu.agh.cast.model.mapper;
19  
20  import java.sql.PreparedStatement;
21  import java.sql.ResultSet;
22  import java.sql.SQLException;
23  import java.sql.Statement;
24  import java.sql.Timestamp;
25  import java.sql.Types;
26  import java.text.SimpleDateFormat;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.Date;
30  import java.util.HashMap;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.SortedMap;
36  
37  /**
38   * <p>
39   * Metamodel node.
40   * </p>
41   *
42   * <p>
43   * Metamodel consists of nodes and links. It allows any upper-level data structure to be stored in the DB, without
44   * modifying its scheme or data warehouse interface.
45   * </p>
46   * <p>
47   * Nodes represents any meaningful element of the model, i.e. object or value of the object. Relations between these
48   * elements are stored in the <ref>Link</ref> class
49   * </p>
50   *
51   *
52   * @author AGH CAST Team
53   *
54   */
55  public class Node {
56  
57  	private static double delta = 0.000001;
58  
59  	private static boolean eager = true;
60  
61  	private List<Link> srcLinks = new ArrayList<Link>();
62  
63  	private List<Link> dstLinks = new ArrayList<Link>();
64  
65  	private String typeName;
66  
67  	private Object value;
68  
69  	private long id;
70  
71  	private boolean saved = false;
72  
73  	private static Mapper mapper = Mapper.getInstance();
74  
75  	private static Map<Long, Node> nodeCache = Collections.synchronizedMap(new HashMap<Long, Node>());
76  
77  	/**
78  	 * Parameterized constructor.
79  	 *
80  	 * @param args
81  	 *            The map of attributes.
82  	 */
83  	public Node(Map args) {
84  		JRubyIntegration.stringifyKeys(args);
85  		initialize((String)args.get("type_name"), null); //$NON-NLS-1$
86  	}
87  
88  	/**
89  	 * Parameterized constructor.
90  	 *
91  	 * @param typeName
92  	 *            The name of the type of node.
93  	 */
94  	public Node(String typeName) {
95  		initialize(typeName, null);
96  	}
97  
98  	/**
99  	 * Parameterized constructor.
100 	 *
101 	 * @param typeName
102 	 *            The name of the type of the node.
103 	 * @param value
104 	 *            The value of the node.
105 	 */
106 	public Node(String typeName, Object value) {
107 		initialize(typeName, value);
108 	}
109 
110 	private void initialize(String newTypeName, Object newValue) {
111 		typeName = newTypeName;
112 		if (newValue != null) {
113 			setValue(newValue);
114 		}
115 	}
116 
117 	/**
118 	 * Type name getter. Returns the type name of the node, i.e. the name of the type which is held by this node. It
119 	 * might be FQCN, but doesn't have to - polymorphic mappings are allowed (i.e. node with given type may be mapped to
120 	 * different java classes).
121 	 *
122 	 * @return The name of the type of node.
123 	 */
124 	public String getTypeName() {
125 		return typeName;
126 	}
127 
128 	// ///////////////////////////////////////////////
129 	// Values
130 	// ///////////////////////////////////////////////
131 	/**
132 	 * Node value getter.
133 	 *
134 	 * @return The value of the node
135 	 */
136 	public Object getValue() {
137 		return value;
138 	}
139 
140 	/**
141 	 * Node value setter.
142 	 *
143 	 * @param newValue
144 	 *            The value to be set
145 	 */
146 	public void setValue(Object newValue) {
147 		if (newValue == null) {
148 			return;
149 		}
150 		if (!(newValue instanceof Long || newValue instanceof Double || newValue instanceof Integer
151 		        || newValue instanceof Boolean || newValue instanceof String || newValue instanceof Date)) {
152 			throw new IllegalArgumentException(newValue.getClass().toString());
153 		}
154 		this.value = newValue;
155 	}
156 
157 	public void setValue(long newValue) {
158 		this.value = new Long(newValue);
159 	}
160 
161 	public void setValue(int newValue) {
162 		this.value = new Long(newValue);
163 	}
164 
165 	/**
166 	 * The value getter.
167 	 *
168 	 * @return The value of the node.
169 	 */
170 	public Long getLongValue() {
171 		if (value instanceof Long) {
172 			return (Long)value;
173 		}
174 		if (value instanceof Integer) {
175 			return new Long((Integer)value);
176 		}
177 		return null;
178 	}
179 
180 	/**
181 	 * The value setter.
182 	 *
183 	 * @param newValue
184 	 *            The value
185 	 */
186 	public void setValue(double newValue) {
187 		this.value = new Double(newValue);
188 	}
189 
190 	/**
191 	 * Typed value getter.
192 	 *
193 	 * @return The value as double.
194 	 */
195 	public Double getDoubleValue() {
196 		if (value instanceof Double) {
197 			return (Double)value;
198 		}
199 		return null;
200 	}
201 
202 	/**
203 	 * The value setter.
204 	 *
205 	 * @param value
206 	 *            The value to set.
207 	 */
208 	public void setValue(boolean value) {
209 		this.value = new Boolean(value);
210 	}
211 
212 	/**
213 	 * Typed value getter.
214 	 *
215 	 * @return The value as boolean.
216 	 */
217 	public Boolean getBooleanValue() {
218 		if (value instanceof Boolean) {
219 			return (Boolean)value;
220 		}
221 		return null;
222 	}
223 
224 	/**
225 	 * The value setter.
226 	 *
227 	 * @param value
228 	 *            The value as string.
229 	 */
230 	public void setValue(String value) {
231 		this.value = value;
232 	}
233 
234 	/**
235 	 * Typed value getter.
236 	 *
237 	 * @return The value as string.
238 	 */
239 	public String getStringValue() {
240 		if (value instanceof String) {
241 			return (String)value;
242 		}
243 		return null;
244 	}
245 
246 	/**
247 	 * The value setter.
248 	 *
249 	 * @param value
250 	 *            The value as date.
251 	 */
252 	public void setValue(Date value) {
253 		this.value = value;
254 	}
255 
256 	/**
257 	 * Type value getter.
258 	 *
259 	 * @return The value as date.
260 	 */
261 	public Date getDateValue() {
262 		if (value instanceof Date) {
263 			return (Date)value;
264 		}
265 		return null;
266 	}
267 
268 	// ///////////////////////////////////////////////
269 	// Links
270 	// ///////////////////////////////////////////////
271 
272 	public List<Link> getSrcLinks() {
273 		return srcLinks;
274 	}
275 
276 	/**
277 	 * The source links setter.
278 	 *
279 	 * @param links
280 	 *            Links to set.
281 	 */
282 	public void setSrcLinks(Set<Link> links) {
283 		srcLinks.clear();
284 		srcLinks.addAll(links);
285 	}
286 
287 	/**
288 	 * The target links getter.
289 	 *
290 	 * @return Target links
291 	 */
292 	public List<Link> getDstLinks() {
293 		return dstLinks;
294 	}
295 
296 	/**
297 	 * The target links setter.
298 	 *
299 	 * @param links
300 	 *            Links to set
301 	 */
302 	public void setDstLinks(Set<Link> links) {
303 		dstLinks.clear();
304 		dstLinks.addAll(links);
305 	}
306 
307 	/**
308 	 * Link creator. Links this node with given node.
309 	 *
310 	 * @param node
311 	 *            The node to link.
312 	 * @param direction
313 	 *            The direction of the link (as seen form "this" node).
314 	 * @param name
315 	 *            The name of the link.
316 	 */
317 	public void link(Node node, Object direction, String name) {
318 		link(node, translateDirection(direction), name, Priority.NONE);
319 	}
320 
321 	/**
322 	 * Links this node with given node.
323 	 *
324 	 * @param node
325 	 *            The node to be linked
326 	 * @param direction
327 	 *            The direction of the link
328 	 * @param name
329 	 *            The name of the link
330 	 * @param priority
331 	 *            The priority of the link
332 	 */
333 	public void link(Node node, Direction direction, String name, Priority priority) {
334 		Link link = new Link(null, null, name, priority);
335 		if (direction == Direction.SRC) {
336 			link.setSrc(this);
337 			link.setDst(node);
338 			srcLinks.add(link);
339 		} else if (direction == Direction.DST) {
340 			link.setDst(this);
341 			link.setSrc(node);
342 			dstLinks.add(link);
343 		} else {
344 			throw new IllegalArgumentException("Direction must be specified"); //$NON-NLS-1$
345 		}
346 		if (saved) {
347 			link.save();
348 		}
349 	}
350 
351 	/**
352 	 * Links accessor.
353 	 *
354 	 * @param name
355 	 *            The name of the links
356 	 * @param directionObj
357 	 *            The direction of the links
358 	 * @return All links which have given name and direction.
359 	 */
360 	public List<Link> links(String name, Object directionObj) {
361 		return links(name, translateDirection(directionObj));
362 	}
363 
364 	/**
365 	 * Translates direction object to direction Enum. Return SRC by default.
366 	 *
367 	 * @param directionObj
368 	 *            diretion object
369 	 * @return Direction as Enum
370 	 */
371 	private Direction translateDirection(Object directionObj) {
372 		if (directionObj.toString().equals("dst")) {
373 			return Direction.DST;
374 		}
375 		return Direction.SRC;
376 	}
377 
378 	/**
379 	 * Returns links with given name.
380 	 *
381 	 * @param name
382 	 *            The name of links
383 	 * @return Links matchi given criteria
384 	 */
385 	public List<Link> links(String name) {
386 		return links(name, null);
387 	}
388 
389 	/**
390 	 * Find all links with given name, with direction direction. If direction is null, all links with given name are
391 	 * returned.
392 	 *
393 	 * @param name
394 	 *            The name of links
395 	 * @param direction
396 	 *            The direction of links
397 	 * @return Links matching given criteria.
398 	 */
399 	public List<Link> links(String name, Direction direction) {
400 		List<Link> sources = new LinkedList<Link>();
401 		for (Link link : srcLinks) {
402 			if (link.getName().equals(name)) {
403 				sources.add(link);
404 			}
405 		}
406 		List<Link> destinations = new LinkedList<Link>();
407 		for (Link link : dstLinks) {
408 			if (link.getName().equals(name)) {
409 				destinations.add(link);
410 			}
411 		}
412 		if (direction == Direction.SRC) {
413 			return sources;
414 		} else if (direction == Direction.DST) {
415 			return destinations;
416 		}
417 		sources.addAll(destinations);
418 		return sources;
419 	}
420 
421 	/**
422 	 * Add dependent node. Links this node with given node as its child.
423 	 *
424 	 * @param node
425 	 *            The node to be linked
426 	 * @param linkName
427 	 *            The name of the link
428 	 * @return the created link
429 	 */
430 	public Link addDependent(Node node, String linkName) {
431 		Link link = new Link(node, this, linkName, Priority.DST_OVER_SRC);
432 		return link;
433 	}
434 
435 	/**
436 	 * String representation of the core data
437 	 *
438 	 * @return The representation.
439 	 */
440 	private String typeNameWithValue() {
441 		StringBuilder str = new StringBuilder();
442 		str.append(typeName);
443 		str.append("["); //$NON-NLS-1$
444 		str.append(id);
445 		if (value != null) {
446 			str.append(","); //$NON-NLS-1$
447 			str.append(value);
448 		}
449 		str.append("]\n"); //$NON-NLS-1$
450 		return str.toString();
451 	}
452 
453 	/**
454 	 *
455 	 * {@inheritDoc}
456 	 *
457 	 * @see java.lang.Object#toString()
458 	 */
459 	@Override
460 	public String toString() {
461 		StringBuilder str = new StringBuilder();
462 		str.append(typeNameWithValue());
463 		str.append("  > \n"); //$NON-NLS-1$
464 		for (Link link : srcLinks) {
465 			if (link.getDst() != null) {
466 				str.append(link.getDst().typeNameWithValue());
467 			}
468 		}
469 		str.append("  < \n"); //$NON-NLS-1$
470 		for (Link link : dstLinks) {
471 			if (link.getSrc() != null) {
472 				str.append(link.getSrc().typeNameWithValue());
473 			}
474 		}
475 		return str.toString();
476 	}
477 
478 	private static PreparedStatement findByIdStmt;
479 
480 	private static PreparedStatement findByIdStmtWithLinks;
481 
482 	private static PreparedStatement findByNameWithZeroLinks;
483 
484 	private static PreparedStatement findByIdStmtWithZeroLinks;
485 
486 	private static PreparedStatement findByIdStmtWithAttrs;
487 
488 	private static PreparedStatement findAttributeByIdStmt;
489 
490 	private static PreparedStatement findByName;
491 
492 	private static PreparedStatement findByNameWithLinks;
493 
494 	private static PreparedStatement insertNodeStmt;
495 
496 	private static PreparedStatement updateNodeStmt;
497 
498 	static {
499 		String stmtStr;
500 		try {
501 			stmtStr = "SELECT id, type_name, int_value, float_value, " //$NON-NLS-1$
502 			        + "string_value, date_value, boolean_value FROM nodes WHERE id = ?"; //$NON-NLS-1$
503 			findByIdStmt = mapper.getConnection().prepareStatement(stmtStr);
504 
505 			stmtStr = "SELECT n1.id, n1.type_name, null, null, " //$NON-NLS-1$
506 			        + "null, null, null, " //$NON-NLS-1$
507 			        + "l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
508 			        + "l1.src_accessor, l1.dst_accessor, l1.src_position, l1.dst_position " //$NON-NLS-1$
509 			        + "FROM nodes n1 INNER JOIN links l1 ON (n1.id = l1.src_id OR n1.id = l1.dst_id) " //$NON-NLS-1$
510 			        + "WHERE n1.id = ? AND l1.src_accessor IS NOT NULL "; //$NON-NLS-1$
511 
512 			findByIdStmtWithLinks = mapper.getConnection().prepareStatement(stmtStr);
513 
514 			stmtStr = "SELECT n1.id, n1.type_name, n1.int_value, n1.float_value, " //$NON-NLS-1$
515 			        + "n1.string_value, n1.date_value, n1.boolean_value, " //$NON-NLS-1$
516 			        + "l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
517 			        + "l1.src_accessor, l1.dst_accessor, l1.src_position, l1.dst_position " //$NON-NLS-1$
518 			        + "FROM nodes n1 INNER JOIN links l1 ON (n1.id = l1.src_id OR n1.id = l1.dst_id) " //$NON-NLS-1$
519 			        + "WHERE n1.id = ? AND l1.src_accessor IS NULL "; //$NON-NLS-1$
520 
521 			findAttributeByIdStmt = mapper.getConnection().prepareStatement(stmtStr);
522 
523 			stmtStr = "SELECT l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
524 			        + "null, null, 0, 0, " //$NON-NLS-1$
525 			        + "n2.id, n2.type_name, n2.int_value, n2.float_value, " //$NON-NLS-1$
526 			        + "n2.string_value, n2.date_value, n2.boolean_value " //$NON-NLS-1$
527 			        + "FROM links l1, nodes n2 " //$NON-NLS-1$
528 			        + "WHERE ((l1.src_id = ? AND n2.id = l1.dst_id) OR " //$NON-NLS-1$
529 			        + "(l1.dst_id = ? AND n2.id = l1.src_id)) " //$NON-NLS-1$
530 			        + "AND l1.src_accessor IS NULL "; //$NON-NLS-1$
531 
532 			findByIdStmtWithAttrs = mapper.getConnection().prepareStatement(stmtStr);
533 
534 			stmtStr = "SELECT id, type_name, int_value, float_value, " //$NON-NLS-1$
535 			        + "string_value, date_value, boolean_value FROM nodes " //$NON-NLS-1$
536 			        + "WHERE type_name = ?"; //$NON-NLS-1$
537 			findByName = mapper.getConnection().prepareStatement(stmtStr);
538 
539 			stmtStr = "SELECT n1.id, n1.type_name, n1.int_value, n1.float_value, " //$NON-NLS-1$
540 			        + "n1.string_value, n1.date_value, n1.boolean_value, " //$NON-NLS-1$
541 			        + "l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
542 			        + "l1.src_accessor, l1.dst_accessor, l1.src_position, l1.dst_position, " //$NON-NLS-1$
543 			        + "n2.id, n2.type_name, n2.int_value, n2.float_value, " //$NON-NLS-1$
544 			        + "n2.string_value, n2.date_value, n2.boolean_value " //$NON-NLS-1$
545 			        + "FROM nodes n1, links l1, nodes n2 " //$NON-NLS-1$
546 			        + "WHERE ((n1.id = l1.src_id AND n2.id = l1.dst_id) OR " //$NON-NLS-1$
547 			        + "(n1.id = l1.dst_id AND n2.id = l1.src_id)) " //$NON-NLS-1$
548 			        + "AND n1.type_name = ? ORDER BY n1.id"; //$NON-NLS-1$
549 
550 			stmtStr = "SELECT n1.id, n1.type_name, null, null, " //$NON-NLS-1$
551 			        + "null, null, null, " //$NON-NLS-1$
552 			        + "l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
553 			        + "l1.src_accessor, l1.dst_accessor, l1.src_position, l1.dst_position " //$NON-NLS-1$
554 			        + "FROM nodes n1 INNER JOIN links l1 ON (n1.id = l1.src_id OR n1.id = l1.dst_id)" //$NON-NLS-1$
555 			        + "WHERE n1.type_name = ? AND l1.src_accessor IS NOT NULL " //$NON-NLS-1$
556 			        + "ORDER BY n1.id"; //$NON-NLS-1$
557 
558 			findByNameWithLinks = mapper.getConnection().prepareStatement(stmtStr);
559 
560 			stmtStr = "SELECT n1.id, n1.type_name, int_value, float_value, " //$NON-NLS-1$
561 			        + "string_value, date_value, boolean_value " //$NON-NLS-1$
562 			        + "FROM nodes n1 " //$NON-NLS-1$
563 			        + "WHERE n1.type_name = ? " //$NON-NLS-1$
564 			        + "ORDER BY n1.id"; //$NON-NLS-1$
565 
566 			findByNameWithZeroLinks = mapper.getConnection().prepareStatement(stmtStr);
567 
568 			stmtStr = "SELECT n1.id, n1.type_name, int_value, float_value, " //$NON-NLS-1$
569 			        + "string_value, date_value, boolean_value " //$NON-NLS-1$
570 			        + "FROM nodes n1 " //$NON-NLS-1$
571 			        + "WHERE n1.id = ? "; //$NON-NLS-1$
572 
573 			findByIdStmtWithZeroLinks = mapper.getConnection().prepareStatement(stmtStr);
574 
575 			stmtStr = "INSERT INTO nodes SET type_name = ?, " //$NON-NLS-1$
576 			        + "int_value = ?, float_value = ?, string_value = ?, date_value = ?, " //$NON-NLS-1$
577 			        + "boolean_value = ? "; //$NON-NLS-1$
578 
579 			insertNodeStmt = mapper.getConnection().prepareStatement(stmtStr, Statement.RETURN_GENERATED_KEYS);
580 
581 			stmtStr = "UPDATE nodes SET type_name = ?, " //$NON-NLS-1$
582 			        + "int_value = ?, float_value = ?, string_value = ?, date_value = ?, " //$NON-NLS-1$
583 			        + "boolean_value = ? WHERE id = ? LIMIT 1"; //$NON-NLS-1$
584 
585 			updateNodeStmt = mapper.getConnection().prepareStatement(stmtStr, Statement.RETURN_GENERATED_KEYS);
586 
587 		} catch (SQLException e) {
588 			e.printStackTrace();
589 		}
590 	}
591 
592 	/**
593 	 * Save metamodel node in the DB.
594 	 * <p>
595 	 * The saving process is recursive. The node is saved first and then all source and destination links are saved.
596 	 * </p>
597 	 * TODO implement transactions
598 	 *
599 	 * @return True if the node was saved successfully or it's been saved earlier.
600 	 */
601 	public boolean save() {
602 		if (saved) {
603 			return true;
604 			// System.out.println("Save node ");
605 		}
606 
607 		// validation
608 		if (typeName == null || typeName.equals("")) { //$NON-NLS-1$
609 			throw new IllegalArgumentException("Node's type name must not be blank: " + this); //$NON-NLS-1$
610 		}
611 
612 		try {
613 			if (id == 0) {
614 				// new node
615 
616 				setValues(insertNodeStmt);
617 
618 				insertNodeStmt.executeUpdate();
619 				ResultSet rs = insertNodeStmt.getGeneratedKeys();
620 				if (rs.next()) {
621 					setId(rs.getLong(1));
622 					setSaved(true);
623 				} else {
624 					throw new SQLException("Cannot obtain the last node id"); //$NON-NLS-1$
625 				}
626 
627 				// links
628 
629 				for (Link link : getSrcLinks()) {
630 					link.save();
631 				}
632 				for (Link link : getDstLinks()) {
633 					link.save();
634 				}
635 				// conn.commit();
636 				return true;
637 			} else {
638 				// update
639 				setValues(updateNodeStmt);
640 				updateNodeStmt.setLong(7, id);
641 				updateNodeStmt.executeUpdate();
642 				setSaved(true);
643 				// TODO update links (?)
644 			}
645 		} catch (SQLException e) {
646 			e.printStackTrace();
647 			setSaved(false);
648 			// try {
649 			// //conn.rollback();
650 			// } catch (SQLException e1) {
651 			// e1.printStackTrace();
652 			// }
653 			return false;
654 		}
655 
656 		return false;
657 
658 	}
659 
660 	/**
661 	 * Returns node with given id or null if not found.
662 	 *
663 	 * @param nodeId
664 	 *            The id of the node
665 	 * @return The node or null
666 	 */
667 	public static Node find(long nodeId) {
668 		// System.out.println("Find node "+nodeId);
669 		Node node;
670 
671 		// try cache first
672 		// try {
673 		node = nodeCache.get(nodeId);
674 		if (node != null) {
675 			// node.reloadLinks();
676 			return node;
677 		}
678 		// } catch (SQLException e) {
679 		// e.printStackTrace();
680 		// }
681 
682 		try {
683 			PreparedStatement stmt;
684 			if (eager) {
685 				stmt = findByIdStmtWithLinks;
686 			} else {
687 				stmt = findByIdStmt;
688 			}
689 
690 			stmt.setLong(1, nodeId);
691 			ResultSet resultSet = stmt.executeQuery();
692 
693 			if (!resultSet.next()) {
694 				if (eager) {
695 					findByIdStmtWithZeroLinks.setLong(1, nodeId);
696 					resultSet = findByIdStmtWithZeroLinks.executeQuery();
697 					if (!resultSet.next()) {
698 						throw new RuntimeException("Node with id = " + nodeId //$NON-NLS-1$
699 						        + " not found"); //$NON-NLS-1$
700 					}
701 				} else {
702 					throw new RuntimeException("Node with id = " + nodeId //$NON-NLS-1$
703 					        + " not found"); //$NON-NLS-1$
704 				}
705 			}
706 
707 			node = loadNode(resultSet, eager);
708 			return node;
709 		} catch (SQLException e) {
710 			e.printStackTrace();
711 		}
712 		return null;
713 	}
714 
715 	/**
716 	 * Find all nodes with given type name.
717 	 *
718 	 * @param typeName
719 	 *            The name of the type
720 	 * @return Nodes with given type name
721 	 */
722 	public static List<Node> find(String typeName) {
723 		List<Node> nodes = new LinkedList<Node>();
724 
725 		try {
726 			PreparedStatement stmt;
727 			if (eager) {
728 				stmt = findByNameWithLinks;
729 			} else {
730 				stmt = findByName;
731 			}
732 			stmt.setString(1, typeName);
733 			ResultSet rs = stmt.executeQuery();
734 			if (eager && !rs.next()) {
735 				// there might be one node with zero links - try to find it
736 				findByNameWithZeroLinks.setString(1, typeName);
737 				rs = findByNameWithZeroLinks.executeQuery();
738 			} else {
739 				rs.previous();
740 			}
741 			while (rs.next()) {
742 				nodes.add(loadNode(rs, eager));
743 			}
744 			return nodes;
745 		} catch (SQLException e) {
746 			e.printStackTrace();
747 		}
748 		return null;
749 	}
750 
751 	/**
752 	 * Find all nodes with given type name which satisfy given set of conditions.
753 	 *
754 	 * @param typeName
755 	 *            The name of the type
756 	 * @param conditions
757 	 *            Map containing pairs: attribute name =&gt; value defining conditions which must be met for model
758 	 *            object for metamodel node to be loaded.
759 	 * @return Nodes with given type name
760 	 */
761 
762 	public static List<Node> find(String typeName, SortedMap<String, Object> conditions) {
763 		List<Node> nodes = new LinkedList<Node>();
764 		Statement findWithConditions = null;
765 
766 		try {
767 			// TODO optimization needed?
768 			StringBuilder stmtBuilder = new StringBuilder();
769 
770 			stmtBuilder.append("SELECT n1.id, n1.type_name, n1.int_value, n1.float_value, " //$NON-NLS-1$
771 			        + "n1.string_value, n1.date_value, n1.boolean_value "); //$NON-NLS-1$
772 			if (eager) {
773 				stmtBuilder.append(", l1.id, l1.src_id, l1.dst_id, l1.priority, l1.name, " //$NON-NLS-1$
774 				        + "l1.src_accessor, l1.dst_accessor, l1.src_position, l1.dst_position "); //$NON-NLS-1$
775 			}
776 			stmtBuilder.append("FROM nodes n1 "); //$NON-NLS-1$
777 			if (eager) {
778 				stmtBuilder.append("INNER JOIN links l1 ON (n1.id = l1.src_id OR n1.id = l1.dst_id) "); //$NON-NLS-1$
779 			}
780 			stmtBuilder.append("WHERE n1.id IN("); //$NON-NLS-1$
781 			subquery(conditions, stmtBuilder);
782 			stmtBuilder.append(") AND type_name='"); //$NON-NLS-1$
783 			stmtBuilder.append(typeName);
784 			stmtBuilder.append("' "); //$NON-NLS-1$
785 			if (eager) {
786 				stmtBuilder.append("AND l1.src_accessor IS NOT NULL"); //$NON-NLS-1$
787 			}
788 
789 			// System.out.println(stmtStr);
790 
791 			findWithConditions = mapper.getConnection().createStatement();
792 
793 			ResultSet rs = findWithConditions.executeQuery(stmtBuilder.toString());
794 
795 			stmtBuilder.delete(0, stmtBuilder.length() - 1);
796 			stmtBuilder = null;
797 
798 			while (rs.next()) {
799 				nodes.add(loadNode(rs, eager));
800 			}
801 			return nodes;
802 		} catch (SQLException e) {
803 			e.printStackTrace();
804 		} finally {
805 			try {
806 				if (findWithConditions != null) {
807 					findWithConditions.close();
808 				}
809 			} catch (SQLException e) {
810 				e.printStackTrace();
811 			}
812 		}
813 		return null;
814 
815 	}
816 
817 	private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); //$NON-NLS-1$
818 
819 	/**
820 	 * Returns SQL subquery which allows to select all nodes, which represent model objects whose attribtes have values
821 	 * set in the conditions map.
822 	 *
823 	 * @param conditions
824 	 *            The conditions to be met. Destroyed during execution! Pairs: attribute name =&gt; value
825 	 * @param stmtBuilder
826 	 * @return The string representing SQL subquery
827 	 */
828 	private static void subquery(SortedMap<String, Object> conditions, StringBuilder stmtBuilder) {
829 		String firstKey = conditions.firstKey();
830 		Object firstValue = conditions.remove(firstKey);
831 
832 		stmtBuilder.append("SELECT dst_id FROM links INNER JOIN nodes ON src_id = nodes.id "); //$NON-NLS-1$
833 		stmtBuilder.append("WHERE name = '"); //$NON-NLS-1$
834 
835 		if (!firstKey.startsWith("get")) { //$NON-NLS-1$
836 			stmtBuilder.append("get"); //$NON-NLS-1$
837 			stmtBuilder.append(firstKey.substring(0, 1).toUpperCase());
838 			stmtBuilder.append(firstKey.substring(1));
839 		} else {
840 			stmtBuilder.append(firstKey);
841 		}
842 
843 		stmtBuilder.append("' AND "); //$NON-NLS-1$
844 
845 		if ((firstValue instanceof Long) || (firstValue instanceof Integer)) {
846 			stmtBuilder.append("int_value = "); //$NON-NLS-1$
847 			stmtBuilder.append(firstValue);
848 		} else if (firstValue instanceof String) {
849 			stmtBuilder.append("string_value = '"); //$NON-NLS-1$
850 			stmtBuilder.append(firstValue);
851 			stmtBuilder.append("'"); //$NON-NLS-1$
852 		} else if (firstValue instanceof Double) {
853 			stmtBuilder.append("float_value = "); //$NON-NLS-1$
854 			stmtBuilder.append(firstValue);
855 		} else if (firstValue instanceof java.util.Date) {
856 			stmtBuilder.append("date_value = '"); //$NON-NLS-1$
857 			stmtBuilder.append(SIMPLE_DATE_FORMAT.format((java.util.Date)firstValue));
858 			stmtBuilder.append("'"); //$NON-NLS-1$
859 		} else if (firstValue instanceof Boolean) {
860 			if ((Boolean)firstValue) {
861 				stmtBuilder.append("boolean_value = 1"); //$NON-NLS-1$
862 			} else {
863 				stmtBuilder.append("boolean_Value = 0"); //$NON-NLS-1$
864 			}
865 		} else {
866 			throw new RuntimeException("Wrong type of condition value: " //$NON-NLS-1$
867 			        + firstValue + " " + firstValue.getClass()); //$NON-NLS-1$
868 		}
869 
870 		if (conditions.size() != 0) {
871 			stmtBuilder.append(" AND dst_id IN ("); //$NON-NLS-1$
872 			subquery(conditions, stmtBuilder);
873 			stmtBuilder.append(")"); //$NON-NLS-1$
874 		}
875 
876 	}
877 
878 	/**
879 	 * Creates node based on the data found in the DB. The node cache is consulted first to ensure referential integrity
880 	 * (i.e. nodes with the same id are created only once). TODO cache invalidation (needed?)
881 	 *
882 	 * The order of data in the result set is as follows:
883 	 * <ol>
884 	 * <li>id
885 	 * <li>type_name
886 	 * <li>int_value
887 	 * <li>float_value
888 	 * <li>string_value
889 	 * <li>date_value
890 	 * <li>boolean_value
891 	 * </ol>
892 	 *
893 	 *
894 	 * @param resultSet
895 	 *            The data to be used to create the node
896 	 * @param eager
897 	 *            links are loaded eagerly - they must be present in the result set, so there is no need to query do DB.
898 	 * @return New node initialized with the data stored in the DB.
899 	 * @throws SQLException
900 	 */
901 	private static Node loadNode(ResultSet resultSet, boolean eager) throws SQLException {
902 		Node node;
903 
904 		long nodeId = resultSet.getLong(1);
905 		// try node cache first
906 		node = nodeCache.get(nodeId);
907 		if (node != null) {
908 			if (eager) {
909 				// since we don't load links, rows with the same nodeId must be
910 				// skipped
911 				while (resultSet.next()) {
912 					if (resultSet.getLong(1) != nodeId) {
913 						resultSet.previous();
914 						break;
915 					}
916 				}
917 			}
918 			return node;
919 		}
920 
921 		node = createNode(resultSet, 0);
922 
923 		if (eager) {
924 			node.loadLinks(resultSet);
925 		} else {
926 			node.loadSrcLinks();
927 			node.loadDstLinks();
928 		}
929 
930 		return node;
931 	}
932 
933 	/**
934 	 * Creates node based on current row in given result set. The fields of the node are in strict order (id, type,
935 	 * intV, floatV, strV, timeV, booleanV) and starts at offset 'offset'
936 	 *
937 	 * @param resultSet
938 	 *            The result set
939 	 * @param offset
940 	 *            The offset of node's fields
941 	 * @return New node instance
942 	 * @throws SQLException
943 	 */
944 	private static Node createNode(ResultSet resultSet, int offset) throws SQLException {
945 		Node node = new Node(resultSet.getString(offset + 2));
946 		node.setId(resultSet.getLong(offset + 1));
947 		node.setSaved(true);
948 
949 		node.loadValues((Integer)resultSet.getObject(offset + 3), (Float)resultSet.getObject(offset + 4),
950 		        (String)resultSet.getObject(offset + 5), (Timestamp)resultSet.getObject(offset + 6), (Boolean)resultSet
951 		                .getObject(offset + 7));
952 
953 		nodeCache.put(node.getId(), node);
954 		return node;
955 	}
956 
957 	/**
958 	 * Load value of the node. The value of the first non-null attribute is used.
959 	 *
960 	 * @param iValue
961 	 * @param fValue
962 	 * @param sValue
963 	 * @param ttValue
964 	 * @param bValue
965 	 */
966 	private void loadValues(Integer iValue, Float fValue, String sValue, Timestamp ttValue, Boolean bValue) {
967 		// mysql uses Integer
968 		Long lValue = null;
969 		if (iValue != null) {
970 			lValue = new Long(iValue);
971 		}
972 
973 		// mysql uses Float
974 		Double ddValue = null;
975 		if (fValue != null) {
976 			ddValue = new Double(fValue);
977 		}
978 
979 		// mysql uses Timestamp
980 		java.util.Date dValue = null;
981 		if (ttValue != null) {
982 			dValue = new java.util.Date(ttValue.getTime());
983 		}
984 
985 		// max one value != null
986 		if (lValue != null) {
987 			setValue(lValue);
988 		} else if (fValue != null) {
989 			setValue(ddValue);
990 		} else if (sValue != null) {
991 			setValue(sValue);
992 		} else if (dValue != null) {
993 			setValue(dValue);
994 		} else if (bValue != null) {
995 			setValue(bValue);
996 		}
997 	}
998 
999 	/**
1000 	 * Load source links.
1001 	 *
1002 	 * @throws SQLException
1003 	 */
1004 	private void loadSrcLinks() throws SQLException {
1005 		List<Link> links = Link.findWithSrc(id);
1006 		for (Link link : links) {
1007 			addSrcLink(link);
1008 		}
1009 	}
1010 
1011 	/**
1012 	 * Load destination links.
1013 	 *
1014 	 * @throws SQLException
1015 	 */
1016 	private void loadDstLinks() throws SQLException {
1017 		List<Link> links = Link.findWithDst(id);
1018 		for (Link link : links) {
1019 			addDstLink(link);
1020 		}
1021 	}
1022 
1023 	/**
1024 	 * Eagerly load links (they are present in the result set, so there is no need to query the DB).
1025 	 *
1026 	 * @param resultSet
1027 	 * @throws SQLException
1028 	 */
1029 	private void loadLinks(ResultSet resultSet) throws SQLException {
1030 		// result set contains only links with nodes which are not attributes
1031 		// i.e. their src_accessor and dst_accessor is not null
1032 		resultSet.previous();
1033 		while (resultSet.next()) {
1034 			long nodeId = resultSet.getLong(1);
1035 			if (nodeId != this.id) {
1036 				resultSet.previous();
1037 				break;
1038 			}
1039 			if (resultSet.getMetaData().getColumnCount() > 7) {
1040 				Link link = Link.createLink(resultSet, 7);
1041 				if (link.getSrcId() == this.id) {
1042 					link.setSrc(this);
1043 				} else {
1044 					link.setDst(this);
1045 				}
1046 				addLink(link);
1047 			}
1048 		}
1049 
1050 		// load the other link with corresponding values
1051 		findByIdStmtWithAttrs.setLong(1, this.id);
1052 		findByIdStmtWithAttrs.setLong(2, this.id);
1053 		resultSet = findByIdStmtWithAttrs.executeQuery();
1054 		while (resultSet.next()) {
1055 			Link link = Link.createLink(resultSet, 0);
1056 			Node node = createNode(resultSet, 9);
1057 			if (link.getSrcId() == this.id) {
1058 				link.setSrc(this);
1059 				addSrcLink(link);
1060 				link.setDst(node);
1061 				node.addDstLink(link);
1062 			} else {
1063 				link.setDst(this);
1064 				addDstLink(link);
1065 				link.setSrc(node);
1066 				node.addSrcLink(link);
1067 			}
1068 		}
1069 	}
1070 
1071 	/**
1072 	 * Reloads links of given node from DB.
1073 	 *
1074 	 * @throws SQLException
1075 	 */
1076 	public void reloadLinks() throws SQLException {
1077 		srcLinks.clear();
1078 		dstLinks.clear();
1079 		loadSrcLinks();
1080 		loadDstLinks();
1081 	}
1082 
1083 	/**
1084 	 * Finds metamodel node for given model object.
1085 	 *
1086 	 * @param object
1087 	 *            The object for which the node is found
1088 	 * @return Metamodel node representing object.
1089 	 */
1090 	public static Node find(Mappable object) {
1091 		return Node.find(object.getMid());
1092 	}
1093 
1094 	/**
1095 	 * Set node values in the update statement. The order of values in the statement is as follows
1096 	 * <ol>
1097 	 * <li>type_name
1098 	 * <li>int_value
1099 	 * <li>float_value
1100 	 * <li>string_value
1101 	 * <li>date_value
1102 	 * <li>boolean_value
1103 	 * </ol>
1104 	 *
1105 	 * @param stmt
1106 	 *            The statement used for update
1107 	 * @param node
1108 	 *            Node
1109 	 * @throws SQLException
1110 	 */
1111 	private void setValues(PreparedStatement stmt) throws SQLException {
1112 		stmt.setString(1, getTypeName());
1113 		// long
1114 		Long lValue = getLongValue();
1115 		if (lValue != null) {
1116 			stmt.setLong(2, lValue);
1117 		} else {
1118 			stmt.setNull(2, Types.BIGINT);
1119 		}
1120 
1121 		// double
1122 		Double dValue = getDoubleValue();
1123 		if (dValue != null) {
1124 			stmt.setDouble(3, dValue);
1125 		} else {
1126 			stmt.setNull(3, Types.DOUBLE);
1127 		}
1128 
1129 		// string
1130 		String sValue = getStringValue();
1131 		if (sValue != null) {
1132 			stmt.setString(4, sValue);
1133 		} else {
1134 			stmt.setNull(4, Types.VARCHAR);
1135 		}
1136 
1137 		// date
1138 		java.util.Date date = getDateValue();
1139 		if (date != null) {
1140 			stmt.setTimestamp(5, new Timestamp(date.getTime()));
1141 		} else {
1142 			stmt.setNull(5, Types.DOUBLE);
1143 		}
1144 
1145 		// boolean
1146 		Boolean bValue = getBooleanValue();
1147 		if (bValue != null) {
1148 			stmt.setBoolean(6, bValue);
1149 		} else {
1150 			stmt.setNull(6, Types.BOOLEAN);
1151 		}
1152 	}
1153 
1154 	/**
1155 	 * Saved flag setter.
1156 	 *
1157 	 * @param saved
1158 	 *            The saved flag
1159 	 */
1160 	public void setSaved(boolean saved) {
1161 		this.saved = saved;
1162 	}
1163 
1164 	/**
1165 	 * Find node which is the same as the given node in terms of their id.
1166 	 *
1167 	 * @param node
1168 	 *            The node to find.
1169 	 * @return The node with the same id.
1170 	 */
1171 	public static Node find(Node node) {
1172 		return find(node.getId());
1173 	}
1174 
1175 	/**
1176 	 * Finds all nodes with given type name.
1177 	 *
1178 	 * @param typeName
1179 	 *            The type name of the node.
1180 	 * @return The list of nodes.
1181 	 */
1182 	public static List<Node> findAllByTypeName(String typeName) {
1183 		return Node.find(typeName);
1184 	}
1185 
1186 	/**
1187 	 * DB identifier getter.
1188 	 *
1189 	 * @return DB identifier
1190 	 */
1191 	public long getId() {
1192 		return id;
1193 	}
1194 
1195 	/**
1196 	 * DB identifier setter - only default access.
1197 	 *
1198 	 * @param nodeId
1199 	 */
1200 	void setId(long nodeId) {
1201 		id = nodeId;
1202 	}
1203 
1204 	/**
1205 	 *
1206 	 * {@inheritDoc}
1207 	 *
1208 	 * @see java.lang.Object#equals(java.lang.Object)
1209 	 */
1210 	@Override
1211 	public boolean equals(Object other) {
1212 		if (this == other) {
1213 			return true;
1214 		}
1215 
1216 		if (!(other instanceof Node)) {
1217 			return false;
1218 		}
1219 		Node that = (Node)other;
1220 
1221 		// value may be null
1222 		if (this.value == null) {
1223 			if (that.value != null) {
1224 				return false;
1225 			}
1226 		} else {
1227 			if (this.value instanceof Double && that.value instanceof Double) {
1228 				Double d1 = (Double)this.value;
1229 				Double d2 = (Double)that.value;
1230 				if (Math.abs(d1 - d2) > delta) {
1231 					return false;
1232 				}
1233 			} else {
1234 				if (!this.value.equals(that.value)) {
1235 					return false;
1236 				}
1237 			}
1238 		}
1239 
1240 		return this.typeName.equals(that.typeName) && this.srcLinks.equals(that.srcLinks)
1241 		        && this.dstLinks.equals(that.dstLinks) && this.id == that.id;
1242 		// TODO if id == 0 two nodes are equal only if their object_id is the
1243 		// same (?)
1244 		// rather the other way around!
1245 	}
1246 
1247 	/**
1248 	 *
1249 	 * {@inheritDoc}
1250 	 *
1251 	 * @see java.lang.Object#hashCode()
1252 	 */
1253 	@Override
1254 	public int hashCode() {
1255 		int result = (int)(typeName.hashCode() ^ (srcLinks.hashCode() * 3) ^ (dstLinks.hashCode() * 5) ^ (id * 11));
1256 		if (value != null) {
1257 			result ^= value.hashCode() * 13;
1258 		}
1259 		return result;
1260 	}
1261 
1262 	/**
1263 	 * Add source link.
1264 	 *
1265 	 * @param link
1266 	 *            Link to add
1267 	 */
1268 	public void addSrcLink(Link link) {
1269 		srcLinks.add(link);
1270 	}
1271 
1272 	/**
1273 	 * Add destination link.
1274 	 *
1275 	 * @param link
1276 	 *            Link to add
1277 	 */
1278 	public void addDstLink(Link link) {
1279 		dstLinks.add(link);
1280 	}
1281 
1282 	/**
1283 	 * Add link to this node. The src/dstPosition and src/dstId are considered when the link is added.
1284 	 *
1285 	 * The link is added as srcLink if the srcId of the link is the same as the id of this node and as dstLink if the
1286 	 * dstId of the link is the same as the id of this node. Otherwise IllegalArgumentException is thrown.
1287 	 *
1288 	 * The position of the link in the list (say _srcLinks) is preserved according to its position attribute (in case of
1289 	 * _srcLinks - dstPostion !! value). This might not equal the actual position in the links list, especially during
1290 	 * the reconstruction of the node.
1291 	 *
1292 	 * @param link
1293 	 *            Link to add
1294 	 */
1295 	public void addLink(Link link) {
1296 		if (link.getSrcId() == this.id) {
1297 			// TODO optimization needed?
1298 			for (Link cLink : srcLinks) {
1299 				if (cLink.getDstPosition() >= link.getDstPosition()) {
1300 					srcLinks.add(srcLinks.indexOf(cLink), link);
1301 					return;
1302 				}
1303 			}
1304 			srcLinks.add(link);
1305 		} else if (link.getDstId() == this.id) {
1306 			// TODO optimization needed?
1307 			for (Link cLink : dstLinks) {
1308 				if (cLink.getSrcPosition() >= link.getSrcPosition()) {
1309 					dstLinks.add(dstLinks.indexOf(cLink), link);
1310 					return;
1311 				}
1312 			}
1313 			dstLinks.add(link);
1314 		} else {
1315 			throw new IllegalArgumentException("The source or destination of the link " + //$NON-NLS-1$
1316 			        "must equal to this node id [" + this.id + "]"); //$NON-NLS-1$ //$NON-NLS-2$
1317 		}
1318 	}
1319 
1320 	/**
1321 	 * Prints the cache status.
1322 	 */
1323 	public static void stats() {
1324 		// TODO DO NOT USE PRINTLN
1325 		// System.out.println("Node cache " + _nodeCache.size()); //$NON-NLS-1$
1326 	}
1327 
1328 	static void clearCache() {
1329 		nodeCache.clear();
1330 	}
1331 }