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 private static final String[] NO_MIXINS = {}; 152 153 /** 154 * Gets the instance mixins. Mixins from the type are not returned. 155 * <p> 156 * Never returns {@code null}. 157 */ 158 public String[] getMixinTypes() { 159 String[] value = (String[]) hierFragment.get(model.MAIN_MIXIN_TYPES_KEY); 160 return value == null ? NO_MIXINS : value.clone(); 161 } 162 163 /** 164 * Gets the mixins. Includes mixins from the type. Returns a fresh set. 165 */ 166 public Set<String> getAllMixinTypes() { 167 // linked for deterministic result 168 Set<String> mixins = new LinkedHashSet<String>(model.getDocumentTypeFacets(getPrimaryType())); 169 mixins.addAll(Arrays.asList(getMixinTypes())); 170 return mixins; 171 } 172 173 /** 174 * Checks the mixins. Includes mixins from the type. 175 */ 176 public boolean hasMixinType(String mixin) { 177 if (model.getDocumentTypeFacets(getPrimaryType()).contains(mixin)) { 178 return true; // present in type 179 } 180 for (String m : getMixinTypes()) { 181 if (m.equals(mixin)) { 182 return true; // present in node 183 } 184 } 185 return false; 186 } 187 188 /** 189 * Clears the properties cache, used when removing mixins. 190 */ 191 protected void clearCache() { 192 // some properties have now become invalid 193 propertyCache.clear(); 194 } 195 196 // ----- properties ----- 197 198 /** 199 * Gets a simple property from the node, given its name. 200 * 201 * @param name the property name 202 * @return the property 203 * @throws PropertyNotFoundException if the name is invalid 204 */ 205 public SimpleProperty getSimpleProperty(String name) { 206 SimpleProperty property = (SimpleProperty) propertyCache.get(name); 207 if (property == null) { 208 ModelProperty propertyInfo = getPropertyInfo(name); 209 if (propertyInfo == null) { 210 throw new PropertyNotFoundException(name); 211 } 212 property = makeSimpleProperty(name, propertyInfo); 213 propertyCache.put(name, property); 214 } 215 return property; 216 } 217 218 protected SimpleProperty makeSimpleProperty(String name, ModelProperty propertyInfo) { 219 String fragmentName = propertyInfo.fragmentName; 220 Fragment fragment = fragments.get(fragmentName); 221 if (fragment == null) { 222 // lazy fragment, fetch from session 223 RowId rowId = new RowId(fragmentName, getId()); 224 fragment = context.get(rowId, true); 225 fragments.put(fragmentName, fragment); 226 } 227 return new SimpleProperty(name, propertyInfo.propertyType, propertyInfo.readonly, (SimpleFragment) fragment, 228 propertyInfo.fragmentKey); 229 } 230 231 /** 232 * Gets a collection property from the node, given its name. 233 * 234 * @param name the property name 235 * @return the property 236 * @throws PropertyNotFoundException if the name is invalid 237 */ 238 public CollectionProperty getCollectionProperty(String name) { 239 CollectionProperty property = (CollectionProperty) propertyCache.get(name); 240 if (property == null) { 241 ModelProperty propertyInfo = getPropertyInfo(name); 242 if (propertyInfo == null) { 243 throw new PropertyNotFoundException(name); 244 } 245 property = makeCollectionProperty(name, propertyInfo); 246 propertyCache.put(name, property); 247 } 248 return property; 249 } 250 251 protected CollectionProperty makeCollectionProperty(String name, ModelProperty propertyInfo) { 252 String fragmentName = propertyInfo.fragmentName; 253 Fragment fragment = fragments.get(fragmentName); 254 if (fragment == null) { 255 // lazy fragment, fetch from session 256 RowId rowId = new RowId(fragmentName, getId()); 257 fragment = context.get(rowId, true); 258 } 259 if (fragment instanceof CollectionFragment) { 260 return new CollectionProperty(name, propertyInfo.propertyType, propertyInfo.readonly, 261 (CollectionFragment) fragment); 262 } else { 263 fragments.put(fragmentName, fragment); 264 return new CollectionProperty(name, propertyInfo.propertyType, propertyInfo.readonly, 265 (SimpleFragment) fragment, propertyInfo.fragmentKey); 266 } 267 } 268 269 protected ModelProperty getPropertyInfo(String name) { 270 // check primary type 271 ModelProperty propertyInfo = model.getPropertyInfo(getPrimaryType(), name); 272 if (propertyInfo != null) { 273 return propertyInfo; 274 } 275 // check mixins 276 for (String mixin : getMixinTypes()) { 277 propertyInfo = model.getMixinPropertyInfo(mixin, name); 278 if (propertyInfo != null) { 279 return propertyInfo; 280 } 281 } 282 // check proxy schemas 283 if (isProxy()) { 284 propertyInfo = model.getProxySchemasPropertyInfo(name); 285 if (propertyInfo != null) { 286 return propertyInfo; 287 } 288 } 289 return null; 290 } 291 292 public void setSimpleProperty(String name, Object value) { 293 SimpleProperty property = getSimpleProperty(name); 294 property.setValue(value); 295 } 296 297 public void setCollectionProperty(String name, Object[] value) { 298 CollectionProperty property = getCollectionProperty(name); 299 property.setValue(value); 300 } 301 302 // ----- locking ----- 303 304 // ----- lifecycle ----- 305 306 // ----- versioning ----- 307 308 // ----- activities, baselines, configurations ----- 309 310 // ----- shared nodes ----- 311 312 // ----- retention ----- 313 314 /* 315 * ----- equals/hashcode ----- 316 */ 317 318 @Override 319 public boolean equals(Object other) { 320 if (other == this) { 321 return true; 322 } 323 if (other instanceof Node) { 324 return equals((Node) other); 325 } 326 return false; 327 } 328 329 private boolean equals(Node other) { 330 return getId() == other.getId(); 331 } 332 333 @Override 334 public int hashCode() { 335 return getId().hashCode(); 336 } 337 338 @Override 339 public String toString() { 340 return getClass().getSimpleName() + "(uuid=" + getId() + ", name=" + getName() + ", primaryType=" 341 + getPrimaryType() + ", parentId=" + getParentId() + ")"; 342 } 343 344 @Override 345 public Object getSingle(String name) throws PropertyException { 346 return getSimpleProperty(name).getValue(); 347 } 348 349 @Override 350 public Object[] getArray(String name) throws PropertyException { 351 return getCollectionProperty(name).getValue(); 352 } 353 354 @Override 355 public void setSingle(String name, Object value) throws PropertyException { 356 getSimpleProperty(name).setValue(value); 357 } 358 359 @Override 360 public void setArray(String name, Object[] value) throws PropertyException { 361 getCollectionProperty(name).setValue(value); 362 } 363 364}