PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/org.eclipse.ecr.core.storage.sql/src/org/eclipse/ecr/core/storage/sql/Node.java

https://github.com/caffeinborg/org.eclipse.ecr
Java | 396 lines | 259 code | 46 blank | 91 comment | 48 complexity | 65b0683927bb123cbc933a16158b3993 MD5 | raw file
  1. /*
  2. * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
  3. *
  4. * All rights reserved. This program and the accompanying materials
  5. * are made available under the terms of the Eclipse Public License v1.0
  6. * which accompanies this distribution, and is available at
  7. * http://www.eclipse.org/legal/epl-v10.html
  8. *
  9. * Contributors:
  10. * Florent Guillaume
  11. */
  12. package org.eclipse.ecr.core.storage.sql;
  13. import java.io.Serializable;
  14. import java.util.ArrayList;
  15. import java.util.Arrays;
  16. import java.util.LinkedHashSet;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.Map.Entry;
  20. import java.util.Set;
  21. import org.apache.commons.collections.map.ReferenceMap;
  22. import org.eclipse.ecr.core.storage.StorageException;
  23. /**
  24. * A {@code Node} implementation. The actual data is stored in contained objects
  25. * that are {@link Fragment}s.
  26. */
  27. public class Node {
  28. /** The persistence context used. */
  29. private final PersistenceContext context;
  30. private final Model model;
  31. /** The hierarchy/main fragment. */
  32. protected final SimpleFragment hierFragment;
  33. /** Fragment information for each additional mixin or inherited fragment. */
  34. private final FragmentsMap fragments;
  35. /**
  36. * Cache of property objects already retrieved. They are dumb objects, just
  37. * providing an indirection to an underlying {@link Fragment}.
  38. */
  39. private final Map<String, BaseProperty> propertyCache;
  40. private Boolean isVersion;
  41. /**
  42. * Creates a Node.
  43. *
  44. * @param context the persistence context
  45. * @param fragmentGroup the group of fragments for the node
  46. */
  47. @SuppressWarnings("unchecked")
  48. protected Node(PersistenceContext context, FragmentGroup fragmentGroup)
  49. throws StorageException {
  50. this.context = context;
  51. model = context.model;
  52. hierFragment = fragmentGroup.hier;
  53. if (fragmentGroup.fragments == null) {
  54. fragments = new FragmentsMap();
  55. } else {
  56. fragments = fragmentGroup.fragments;
  57. }
  58. // memory-sensitive
  59. propertyCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT);
  60. }
  61. // ----- basics -----
  62. /**
  63. * Gets the node unique id, usually a Long or a String.
  64. *
  65. * @return the node id
  66. */
  67. public Serializable getId() {
  68. /*
  69. * We don't cache the id as it changes between the initial creation and
  70. * the first save.
  71. */
  72. return hierFragment.getId();
  73. }
  74. public String getName() {
  75. try {
  76. return getHierFragment().getString(model.HIER_CHILD_NAME_KEY);
  77. } catch (StorageException e) {
  78. // do not propagate this unlikely exception as a checked one
  79. throw new RuntimeException(e);
  80. }
  81. }
  82. public String getPrimaryType() {
  83. try {
  84. return hierFragment.getString(model.MAIN_PRIMARY_TYPE_KEY);
  85. } catch (StorageException e) {
  86. // do not propagate this unlikely exception as a checked one
  87. throw new RuntimeException(e);
  88. }
  89. }
  90. public String getParentId() {
  91. try {
  92. return getHierFragment().getString(model.HIER_PARENT_KEY);
  93. } catch (StorageException e) {
  94. // do not propagate this unlikely exception as a checked one
  95. throw new RuntimeException(e);
  96. }
  97. }
  98. protected SimpleFragment getHierFragment() {
  99. return hierFragment;
  100. }
  101. // cache the isVersion computation
  102. public boolean isVersion() {
  103. if (isVersion == null) {
  104. try {
  105. isVersion = (Boolean) getSimpleProperty(
  106. model.MAIN_IS_VERSION_PROP).getValue();
  107. } catch (StorageException e) {
  108. throw new RuntimeException(e);
  109. }
  110. if (isVersion == null) {
  111. isVersion = Boolean.FALSE;
  112. }
  113. }
  114. return isVersion.booleanValue();
  115. }
  116. public boolean isProxy() {
  117. return getPrimaryType().equals(model.PROXY_TYPE);
  118. }
  119. private static final String[] NO_MIXINS = {};
  120. /**
  121. * Gets the instance mixins. Mixins from the type are not returned.
  122. * <p>
  123. * Never returns {@code null}.
  124. */
  125. public String[] getMixinTypes() {
  126. try {
  127. String[] value = (String[]) hierFragment.get(model.MAIN_MIXIN_TYPES_KEY);
  128. return value == null ? NO_MIXINS : value.clone();
  129. } catch (StorageException e) {
  130. throw new RuntimeException(e);
  131. }
  132. }
  133. /**
  134. * Gets the mixins. Includes mixins from the type. Returns a fresh set.
  135. */
  136. public Set<String> getAllMixinTypes() {
  137. // linked for deterministic result
  138. Set<String> mixins = new LinkedHashSet<String>(
  139. model.getDocumentTypeFacets(getPrimaryType()));
  140. mixins.addAll(Arrays.asList(getMixinTypes()));
  141. return mixins;
  142. }
  143. /**
  144. * Checks the mixins. Includes mixins from the type.
  145. */
  146. public boolean hasMixinType(String mixin) {
  147. if (model.getDocumentTypeFacets(getPrimaryType()).contains(mixin)) {
  148. return true; // present in type
  149. }
  150. for (String m : getMixinTypes()) {
  151. if (m.equals(mixin)) {
  152. return true; // present in node
  153. }
  154. }
  155. return false;
  156. }
  157. /**
  158. * Adds a mixin.
  159. */
  160. public boolean addMixinType(String mixin) {
  161. if (model.getMixinPropertyInfos(mixin) == null) {
  162. throw new IllegalArgumentException("No such mixin: " + mixin);
  163. }
  164. if (model.getDocumentTypeFacets(getPrimaryType()).contains(mixin)) {
  165. return false; // already present in type
  166. }
  167. List<String> list = new ArrayList<String>(Arrays.asList(getMixinTypes()));
  168. if (list.contains(mixin)) {
  169. return false; // already present in node
  170. }
  171. list.add(mixin);
  172. try {
  173. String[] mixins = list.toArray(new String[list.size()]);
  174. hierFragment.put(model.MAIN_MIXIN_TYPES_KEY, mixins);
  175. } catch (StorageException e) {
  176. throw new RuntimeException(e);
  177. }
  178. return true;
  179. }
  180. /**
  181. * Removes a mixin.
  182. */
  183. public boolean removeMixinType(String mixin) {
  184. List<String> list = new ArrayList<String>(Arrays.asList(getMixinTypes()));
  185. if (!list.remove(mixin)) {
  186. return false; // not present in node
  187. }
  188. try {
  189. String[] mixins = list.toArray(new String[list.size()]);
  190. if (mixins.length == 0) {
  191. mixins = null;
  192. }
  193. hierFragment.put(model.MAIN_MIXIN_TYPES_KEY, mixins);
  194. clearMixinValues(mixin);
  195. } catch (StorageException e) {
  196. throw new RuntimeException(e);
  197. }
  198. return true;
  199. }
  200. protected void clearMixinValues(String mixin) throws StorageException {
  201. for (Entry<String, ModelProperty> en : model.getMixinPropertyInfos(
  202. mixin).entrySet()) {
  203. String name = en.getKey();
  204. if (getPropertyInfo(name) != null) {
  205. // don't clear if still exists in primary type or other
  206. // mixins
  207. continue;
  208. }
  209. ModelProperty propertyInfo = en.getValue();
  210. if (propertyInfo.propertyType.isArray()) {
  211. makeCollectionProperty(name, propertyInfo).setValue(null);
  212. } else {
  213. makeSimpleProperty(name, propertyInfo).setValue(null);
  214. }
  215. }
  216. propertyCache.clear(); // some properties have now become invalid
  217. // TODO optim: delete rows if all null
  218. }
  219. // ----- properties -----
  220. /**
  221. * Gets a simple property from the node, given its name.
  222. *
  223. * @param name the property name
  224. * @return the property
  225. * @throws IllegalArgumentException if the name is invalid
  226. */
  227. public SimpleProperty getSimpleProperty(String name)
  228. throws StorageException {
  229. SimpleProperty property = (SimpleProperty) propertyCache.get(name);
  230. if (property == null) {
  231. ModelProperty propertyInfo = getPropertyInfo(name);
  232. if (propertyInfo == null) {
  233. throw new IllegalArgumentException("Unknown field: " + name);
  234. }
  235. property = makeSimpleProperty(name, propertyInfo);
  236. propertyCache.put(name, property);
  237. }
  238. return property;
  239. }
  240. protected SimpleProperty makeSimpleProperty(String name,
  241. ModelProperty propertyInfo) throws StorageException {
  242. String fragmentName = propertyInfo.fragmentName;
  243. Fragment fragment = fragments.get(fragmentName);
  244. if (fragment == null) {
  245. // lazy fragment, fetch from session
  246. RowId rowId = new RowId(fragmentName, getId());
  247. fragment = context.get(rowId, true);
  248. fragments.put(fragmentName, fragment);
  249. }
  250. return new SimpleProperty(name, propertyInfo.propertyType,
  251. propertyInfo.readonly, (SimpleFragment) fragment,
  252. propertyInfo.fragmentKey);
  253. }
  254. /**
  255. * Gets a collection property from the node, given its name.
  256. *
  257. * @param name the property name
  258. * @return the property
  259. * @throws IllegalArgumentException if the name is invalid
  260. */
  261. public CollectionProperty getCollectionProperty(String name)
  262. throws StorageException {
  263. CollectionProperty property = (CollectionProperty) propertyCache.get(name);
  264. if (property == null) {
  265. ModelProperty propertyInfo = getPropertyInfo(name);
  266. if (propertyInfo == null) {
  267. throw new IllegalArgumentException("Unknown field: " + name);
  268. }
  269. property = makeCollectionProperty(name, propertyInfo);
  270. propertyCache.put(name, property);
  271. }
  272. return property;
  273. }
  274. protected CollectionProperty makeCollectionProperty(String name,
  275. ModelProperty propertyInfo) throws StorageException {
  276. String fragmentName = propertyInfo.fragmentName;
  277. RowId rowId = new RowId(fragmentName, getId());
  278. Fragment fragment = context.get(rowId, true);
  279. CollectionProperty property = new CollectionProperty(name,
  280. propertyInfo.propertyType, false, (CollectionFragment) fragment);
  281. return property;
  282. }
  283. public BaseProperty getProperty(String name) throws StorageException {
  284. BaseProperty property = propertyCache.get(name);
  285. if (property != null) {
  286. return property;
  287. }
  288. ModelProperty propertyInfo = getPropertyInfo(name);
  289. if (propertyInfo == null) {
  290. throw new IllegalArgumentException("Unknown field: " + name);
  291. }
  292. if (propertyInfo.propertyType.isArray()) {
  293. return getCollectionProperty(name);
  294. } else {
  295. return getSimpleProperty(name);
  296. }
  297. }
  298. protected ModelProperty getPropertyInfo(String name) {
  299. // check primary type
  300. ModelProperty propertyInfo = model.getPropertyInfo(getPrimaryType(),
  301. name);
  302. if (propertyInfo != null) {
  303. return propertyInfo;
  304. }
  305. // check mixins
  306. for (String mixin : getMixinTypes()) {
  307. propertyInfo = model.getMixinPropertyInfo(mixin, name);
  308. if (propertyInfo != null) {
  309. return propertyInfo;
  310. }
  311. }
  312. return null;
  313. }
  314. public void setSimpleProperty(String name, Serializable value)
  315. throws StorageException {
  316. SimpleProperty property = getSimpleProperty(name);
  317. property.setValue(value);
  318. }
  319. public void setCollectionProperty(String name, Serializable[] value)
  320. throws StorageException {
  321. CollectionProperty property = getCollectionProperty(name);
  322. property.setValue(value);
  323. }
  324. // ----- locking -----
  325. // ----- lifecycle -----
  326. // ----- versioning -----
  327. // ----- activities, baselines, configurations -----
  328. // ----- shared nodes -----
  329. // ----- retention -----
  330. /*
  331. * ----- equals/hashcode -----
  332. */
  333. @Override
  334. public boolean equals(Object other) {
  335. if (other == this) {
  336. return true;
  337. }
  338. if (other instanceof Node) {
  339. return equals((Node) other);
  340. }
  341. return false;
  342. }
  343. private boolean equals(Node other) {
  344. return getId() == other.getId();
  345. }
  346. @Override
  347. public int hashCode() {
  348. return getId().hashCode();
  349. }
  350. }