001/* 002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012 013package org.nuxeo.ecm.core.storage.sql; 014 015import java.io.Serializable; 016import java.util.Arrays; 017import java.util.LinkedHashSet; 018import java.util.Map; 019import java.util.Set; 020 021import org.apache.commons.collections.map.ReferenceMap; 022import org.nuxeo.ecm.core.api.PropertyException; 023import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 024import org.nuxeo.ecm.core.storage.StateAccessor; 025 026/** 027 * A {@code Node} implementation. The actual data is stored in contained objects that are {@link Fragment}s. 028 */ 029public class Node implements StateAccessor { 030 031 /** The persistence context used. */ 032 private final PersistenceContext context; 033 034 private final Model model; 035 036 /** The hierarchy/main fragment. */ 037 protected final SimpleFragment hierFragment; 038 039 /** Fragment information for each additional mixin or inherited fragment. */ 040 protected final FragmentsMap fragments; 041 042 /** 043 * Path, only for immediate consumption after construction (will be reset to null afterwards). 044 */ 045 protected String path; 046 047 /** 048 * Cache of property objects already retrieved. They are dumb objects, just providing an indirection to an 049 * underlying {@link Fragment}. 050 */ 051 private final Map<String, BaseProperty> propertyCache; 052 053 private Boolean isVersion; 054 055 /** 056 * Creates a Node. 057 * 058 * @param context the persistence context 059 * @param fragmentGroup the group of fragments for the node 060 * @param path the path, if known at construction time 061 */ 062 @SuppressWarnings("unchecked") 063 protected Node(PersistenceContext context, FragmentGroup fragmentGroup, String path) { 064 this.context = context; 065 model = context.model; 066 hierFragment = fragmentGroup.hier; 067 if (fragmentGroup.fragments == null) { 068 fragments = new FragmentsMap(); 069 } else { 070 fragments = fragmentGroup.fragments; 071 } 072 this.path = path; 073 // memory-sensitive 074 propertyCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT); 075 } 076 077 // ----- basics ----- 078 079 /** 080 * Gets the node unique id, usually a Long or a String. 081 * 082 * @return the node id 083 */ 084 public Serializable getId() { 085 /* 086 * We don't cache the id as it changes between the initial creation and the first save. 087 */ 088 return hierFragment.getId(); 089 } 090 091 public String getName() { 092 return getHierFragment().getString(model.HIER_CHILD_NAME_KEY); 093 } 094 095 public Long getPos() { 096 return (Long) getHierFragment().get(model.HIER_CHILD_POS_KEY); 097 } 098 099 public String getPrimaryType() { 100 return hierFragment.getString(model.MAIN_PRIMARY_TYPE_KEY); 101 } 102 103 public Serializable getParentId() { 104 return getHierFragment().get(model.HIER_PARENT_KEY); 105 } 106 107 /** 108 * Gets the path that was assigned at {@link Node} construction time. Then it's reset to {@code null}. Should only 109 * be used once. 110 * 111 * @return the path, or {@code null} for unknown 112 */ 113 public String getPath() { 114 String p = path; 115 if (p != null) { 116 path = null; 117 } 118 return p; 119 } 120 121 protected SimpleFragment getHierFragment() { 122 return hierFragment; 123 } 124 125 // cache the isVersion computation 126 public boolean isVersion() { 127 if (isVersion == null) { 128 isVersion = (Boolean) getSimpleProperty(model.MAIN_IS_VERSION_PROP).getValue(); 129 if (isVersion == null) { 130 isVersion = Boolean.FALSE; 131 } 132 } 133 return isVersion.booleanValue(); 134 } 135 136 public boolean isProxy() { 137 String primaryType = getPrimaryType(); 138 if (primaryType == null) { 139 throw new NullPointerException(this.toString()); 140 } 141 return primaryType.equals(model.PROXY_TYPE); 142 } 143 144 private static final String[] NO_MIXINS = {}; 145 146 /** 147 * Gets the instance mixins. Mixins from the type are not returned. 148 * <p> 149 * Never returns {@code null}. 150 */ 151 public String[] getMixinTypes() { 152 String[] value = (String[]) hierFragment.get(model.MAIN_MIXIN_TYPES_KEY); 153 return value == null ? NO_MIXINS : value.clone(); 154 } 155 156 /** 157 * Gets the mixins. Includes mixins from the type. Returns a fresh set. 158 */ 159 public Set<String> getAllMixinTypes() { 160 // linked for deterministic result 161 Set<String> mixins = new LinkedHashSet<String>(model.getDocumentTypeFacets(getPrimaryType())); 162 mixins.addAll(Arrays.asList(getMixinTypes())); 163 return mixins; 164 } 165 166 /** 167 * Checks the mixins. Includes mixins from the type. 168 */ 169 public boolean hasMixinType(String mixin) { 170 if (model.getDocumentTypeFacets(getPrimaryType()).contains(mixin)) { 171 return true; // present in type 172 } 173 for (String m : getMixinTypes()) { 174 if (m.equals(mixin)) { 175 return true; // present in node 176 } 177 } 178 return false; 179 } 180 181 /** 182 * Clears the properties cache, used when removing mixins. 183 */ 184 protected void clearCache() { 185 // some properties have now become invalid 186 propertyCache.clear(); 187 } 188 189 // ----- properties ----- 190 191 /** 192 * Gets a simple property from the node, given its name. 193 * 194 * @param name the property name 195 * @return the property 196 * @throws PropertyNotFoundException if the name is invalid 197 */ 198 public SimpleProperty getSimpleProperty(String name) { 199 SimpleProperty property = (SimpleProperty) propertyCache.get(name); 200 if (property == null) { 201 ModelProperty propertyInfo = getPropertyInfo(name); 202 if (propertyInfo == null) { 203 throw new PropertyNotFoundException(name); 204 } 205 property = makeSimpleProperty(name, propertyInfo); 206 propertyCache.put(name, property); 207 } 208 return property; 209 } 210 211 protected SimpleProperty makeSimpleProperty(String name, ModelProperty propertyInfo) { 212 String fragmentName = propertyInfo.fragmentName; 213 Fragment fragment = fragments.get(fragmentName); 214 if (fragment == null) { 215 // lazy fragment, fetch from session 216 RowId rowId = new RowId(fragmentName, getId()); 217 fragment = context.get(rowId, true); 218 fragments.put(fragmentName, fragment); 219 } 220 return new SimpleProperty(name, propertyInfo.propertyType, propertyInfo.readonly, (SimpleFragment) fragment, 221 propertyInfo.fragmentKey); 222 } 223 224 /** 225 * Gets a collection property from the node, given its name. 226 * 227 * @param name the property name 228 * @return the property 229 * @throws PropertyNotFoundException if the name is invalid 230 */ 231 public CollectionProperty getCollectionProperty(String name) { 232 CollectionProperty property = (CollectionProperty) propertyCache.get(name); 233 if (property == null) { 234 ModelProperty propertyInfo = getPropertyInfo(name); 235 if (propertyInfo == null) { 236 throw new PropertyNotFoundException(name); 237 } 238 property = makeCollectionProperty(name, propertyInfo); 239 propertyCache.put(name, property); 240 } 241 return property; 242 } 243 244 protected CollectionProperty makeCollectionProperty(String name, ModelProperty propertyInfo) { 245 String fragmentName = propertyInfo.fragmentName; 246 Fragment fragment = fragments.get(fragmentName); 247 if (fragment == null) { 248 // lazy fragment, fetch from session 249 RowId rowId = new RowId(fragmentName, getId()); 250 fragment = context.get(rowId, true); 251 } 252 if (fragment instanceof CollectionFragment) { 253 return new CollectionProperty(name, propertyInfo.propertyType, propertyInfo.readonly, 254 (CollectionFragment) fragment); 255 } else { 256 fragments.put(fragmentName, fragment); 257 return new CollectionProperty(name, propertyInfo.propertyType, propertyInfo.readonly, 258 (SimpleFragment) fragment, propertyInfo.fragmentKey); 259 } 260 } 261 262 protected ModelProperty getPropertyInfo(String name) { 263 // check primary type 264 ModelProperty propertyInfo = model.getPropertyInfo(getPrimaryType(), name); 265 if (propertyInfo != null) { 266 return propertyInfo; 267 } 268 // check mixins 269 for (String mixin : getMixinTypes()) { 270 propertyInfo = model.getMixinPropertyInfo(mixin, name); 271 if (propertyInfo != null) { 272 return propertyInfo; 273 } 274 } 275 // check proxy schemas 276 if (isProxy()) { 277 propertyInfo = model.getProxySchemasPropertyInfo(name); 278 if (propertyInfo != null) { 279 return propertyInfo; 280 } 281 } 282 return null; 283 } 284 285 public void setSimpleProperty(String name, Object value) { 286 SimpleProperty property = getSimpleProperty(name); 287 property.setValue(value); 288 } 289 290 public void setCollectionProperty(String name, Object[] value) { 291 CollectionProperty property = getCollectionProperty(name); 292 property.setValue(value); 293 } 294 295 // ----- locking ----- 296 297 // ----- lifecycle ----- 298 299 // ----- versioning ----- 300 301 // ----- activities, baselines, configurations ----- 302 303 // ----- shared nodes ----- 304 305 // ----- retention ----- 306 307 /* 308 * ----- equals/hashcode ----- 309 */ 310 311 @Override 312 public boolean equals(Object other) { 313 if (other == this) { 314 return true; 315 } 316 if (other instanceof Node) { 317 return equals((Node) other); 318 } 319 return false; 320 } 321 322 private boolean equals(Node other) { 323 return getId() == other.getId(); 324 } 325 326 @Override 327 public int hashCode() { 328 return getId().hashCode(); 329 } 330 331 @Override 332 public String toString() { 333 return getClass().getSimpleName() + "(uuid=" + getId() + ", name=" + getName() + ", primaryType=" 334 + getPrimaryType() + ", parentId=" + getParentId() + ")"; 335 } 336 337 @Override 338 public Object getSingle(String name) throws PropertyException { 339 return getSimpleProperty(name).getValue(); 340 } 341 342 @Override 343 public Object[] getArray(String name) throws PropertyException { 344 return getCollectionProperty(name).getValue(); 345 } 346 347 @Override 348 public void setSingle(String name, Object value) throws PropertyException { 349 getSimpleProperty(name).setValue(value); 350 } 351 352 @Override 353 public void setArray(String name, Object[] value) throws PropertyException { 354 getCollectionProperty(name).setValue(value); 355 } 356 357}