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