blob: 54e85cc37384b2fbd81843087ac28fab4eda7ebc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.structure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.EdgeId;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.backend.serializer.BytesBuffer;
import org.apache.hugegraph.backend.tx.GraphTransaction;
import org.apache.hugegraph.perf.PerfUtil.Watched;
import org.apache.hugegraph.schema.PropertyKey;
import org.apache.hugegraph.schema.SchemaLabel;
import org.apache.hugegraph.schema.VertexLabel;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.Idfiable;
import org.apache.hugegraph.type.define.Cardinality;
import org.apache.hugegraph.type.define.HugeKeys;
import org.apache.hugegraph.util.CollectionUtil;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.InsertionOrderUtil;
import org.apache.hugegraph.util.collection.CollectionFactory;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
import org.eclipse.collections.api.iterator.IntIterator;
import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
public abstract class HugeElement implements Element, GraphType, Idfiable, Comparable<HugeElement> {
private static final MutableIntObjectMap<HugeProperty<?>> EMPTY_MAP =
CollectionFactory.newIntObjectMap();
private static final int MAX_PROPERTIES = BytesBuffer.UINT16_MAX;
private final HugeGraph graph;
private MutableIntObjectMap<HugeProperty<?>> properties;
// TODO: move into properties to keep small object
private long expiredTime;
private boolean removed;
private boolean fresh;
private boolean propLoaded;
private boolean defaultValueUpdated;
public HugeElement(final HugeGraph graph) {
E.checkArgument(graph != null, "HugeElement graph can't be null");
this.graph = graph;
this.properties = EMPTY_MAP;
this.expiredTime = 0L;
this.removed = false;
this.fresh = false;
this.propLoaded = true;
this.defaultValueUpdated = false;
}
public abstract SchemaLabel schemaLabel();
protected abstract GraphTransaction tx();
protected abstract <V> HugeProperty<V> newProperty(PropertyKey pk, V val);
protected abstract <V> void onUpdateProperty(Cardinality cardinality,
HugeProperty<V> prop);
protected abstract boolean ensureFilledProperties(boolean throwIfNotExist);
protected void updateToDefaultValueIfNone() {
if (this.fresh() || this.defaultValueUpdated) {
return;
}
this.defaultValueUpdated = true;
// Set default value if needed
for (Id pkeyId : this.schemaLabel().properties()) {
if (this.properties.containsKey(intFromId(pkeyId))) {
continue;
}
PropertyKey pkey = this.graph().propertyKey(pkeyId);
Object value = pkey.defaultValue();
if (value != null) {
this.setProperty(this.newProperty(pkey, value));
}
}
this.defaultValueUpdated = true;
}
@Override
public HugeGraph graph() {
return this.graph;
}
protected void removed(boolean removed) {
this.removed = removed;
}
public boolean removed() {
return this.removed;
}
protected void fresh(boolean fresh) {
this.fresh = fresh;
}
public boolean fresh() {
return this.fresh;
}
public boolean isPropLoaded() {
return this.propLoaded;
}
protected void propLoaded() {
this.propLoaded = true;
}
public void propNotLoaded() {
this.propLoaded = false;
}
public void forceLoad() {
this.ensureFilledProperties(false);
}
public void committed() {
this.fresh = false;
// Set expired time
this.setExpiredTimeIfNeeded();
}
public void setExpiredTimeIfNeeded() {
SchemaLabel label = this.schemaLabel();
if (label.ttl() == 0L) {
return;
}
long now = this.graph.now();
if (SchemaLabel.NONE_ID.equals(label.ttlStartTime())) {
this.expiredTime(now + label.ttl());
return;
}
Date date = this.getPropertyValue(label.ttlStartTime());
if (date == null) {
this.expiredTime(now + label.ttl());
return;
}
long expired = date.getTime() + label.ttl();
E.checkArgument(expired > now,
"The expired time '%s' of '%s' is prior to now: %s",
new Date(expired), this, now);
this.expiredTime(expired);
}
public long expiredTime() {
return this.expiredTime;
}
public void expiredTime(long expiredTime) {
this.expiredTime = expiredTime;
}
public boolean expired() {
return 0L < this.expiredTime && this.expiredTime < this.graph.now();
}
public long ttl() {
if (this.expiredTime == 0L || this.expiredTime < this.graph.now()) {
return 0L;
}
return this.expiredTime - this.graph.now();
}
public boolean hasTtl() {
return this.schemaLabel().ttl() > 0L;
}
public Set<Id> getPropertyKeys() {
Set<Id> propKeys = InsertionOrderUtil.newSet();
IntIterator keys = this.properties.keysView().intIterator();
while (keys.hasNext()) {
propKeys.add(IdGenerator.of(keys.next()));
}
return propKeys;
}
public Collection<HugeProperty<?>> getProperties() {
return this.properties.values();
}
public Collection<HugeProperty<?>> getFilledProperties() {
this.ensureFilledProperties(true);
return this.getProperties();
}
public Map<Id, Object> getPropertiesMap() {
Map<Id, Object> props = InsertionOrderUtil.newMap();
for (HugeProperty<?> prop : this.properties.values()) {
props.put(prop.propertyKey().id(), prop.value());
}
// TODO: return MutableIntObjectMap<Object> for this method?
return props;
}
public Collection<HugeProperty<?>> getAggregateProperties() {
List<HugeProperty<?>> aggrProps = InsertionOrderUtil.newList();
for (HugeProperty<?> prop : this.properties.values()) {
if (prop.type().isAggregateProperty()) {
aggrProps.add(prop);
}
}
return aggrProps;
}
@SuppressWarnings("unchecked")
public <V> HugeProperty<V> getProperty(Id key) {
return (HugeProperty<V>) this.properties.get(intFromId(key));
}
@SuppressWarnings("unchecked")
public <V> V getPropertyValue(Id key) {
HugeProperty<?> prop = this.properties.get(intFromId(key));
if (prop == null) {
return null;
}
return (V) prop.value();
}
public boolean hasProperty(Id key) {
return this.properties.containsKey(intFromId(key));
}
public boolean hasProperties() {
return this.properties.size() > 0;
}
public int sizeOfProperties() {
return this.properties.size();
}
public int sizeOfSubProperties() {
int size = 0;
for (HugeProperty<?> p : this.properties.values()) {
size++;
if (p.propertyKey().cardinality() != Cardinality.SINGLE &&
p.value() instanceof Collection) {
size += ((Collection<?>) p.value()).size();
}
}
return size;
}
@Watched(prefix = "element")
public <V> HugeProperty<?> setProperty(HugeProperty<V> prop) {
if (this.properties == EMPTY_MAP) {
this.properties = CollectionFactory.newIntObjectMap();
}
PropertyKey pkey = prop.propertyKey();
E.checkArgument(this.properties.containsKey(intFromId(pkey.id())) ||
this.properties.size() < MAX_PROPERTIES,
"Exceeded the maximum number of properties");
return this.properties.put(intFromId(pkey.id()), prop);
}
public <V> HugeProperty<?> removeProperty(Id key) {
return this.properties.remove(intFromId(key));
}
public <V> HugeProperty<V> addProperty(PropertyKey pkey, V value) {
return this.addProperty(pkey, value, false);
}
@Watched(prefix = "element")
public <V> HugeProperty<V> addProperty(PropertyKey pkey, V value,
boolean notify) {
HugeProperty<V> prop = null;
switch (pkey.cardinality()) {
case SINGLE:
prop = this.newProperty(pkey, value);
if (notify) {
/*
* NOTE: this method should be called before setProperty()
* because tx need to delete index without the new property
*/
this.onUpdateProperty(pkey.cardinality(), prop);
}
this.setProperty(prop);
break;
case SET:
prop = this.addProperty(pkey, value, HashSet::new);
if (notify) {
this.onUpdateProperty(pkey.cardinality(), prop);
}
break;
case LIST:
prop = this.addProperty(pkey, value, ArrayList::new);
if (notify) {
this.onUpdateProperty(pkey.cardinality(), prop);
}
break;
default:
assert false;
break;
}
return prop;
}
@Watched(prefix = "element")
@SuppressWarnings({ "rawtypes", "unchecked" })
private <V> HugeProperty<V> addProperty(PropertyKey pkey, V value,
Supplier<Collection<V>> supplier) {
assert pkey.cardinality().multiple();
HugeProperty<Collection<V>> property;
if (this.hasProperty(pkey.id())) {
property = this.getProperty(pkey.id());
} else {
property = this.newProperty(pkey, supplier.get());
this.setProperty(property);
}
Collection<V> values;
if (pkey.cardinality() == Cardinality.SET) {
if (value instanceof Set) {
values = (Set<V>) value;
} else {
values = CollectionUtil.toSet(value);
}
} else {
assert pkey.cardinality() == Cardinality.LIST;
if (value instanceof List) {
values = (List<V>) value;
} else {
values = CollectionUtil.toList(value);
}
}
property.value().addAll(pkey.validValueOrThrow(values));
// Any better ways?
return (HugeProperty) property;
}
public void resetProperties() {
this.properties = CollectionFactory.newIntObjectMap();
this.propLoaded = false;
}
protected void copyProperties(HugeElement element) {
if (element.properties == EMPTY_MAP) {
this.properties = EMPTY_MAP;
} else {
this.properties = CollectionFactory.newIntObjectMap(
element.properties);
}
this.propLoaded = true;
}
public HugeElement copyAsFresh() {
HugeElement elem = this.copy();
elem.fresh = true;
return elem;
}
public abstract HugeElement copy();
public abstract Object sysprop(HugeKeys key);
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Element)) {
return false;
}
Element other = (Element) obj;
if (this.id() == null) {
return false;
}
return this.id().equals(other.id());
}
@Override
public int hashCode() {
E.checkState(this.id() != null, "Element id can't be null");
return ElementHelper.hashCode(this);
}
@Override
public int compareTo(HugeElement o) {
return this.id().compareTo(o.id());
}
/**
* Classify parameter list(pairs) from call request
* @param keyValues The property key-value pair of the vertex or edge
* @return Key-value pairs that are classified and processed
*/
@Watched(prefix = "element")
public static final ElementKeys classifyKeys(Object... keyValues) {
ElementKeys elemKeys = new ElementKeys();
if ((keyValues.length & 1) == 1) {
throw Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo();
}
for (int i = 0; i < keyValues.length; i = i + 2) {
Object key = keyValues[i];
Object val = keyValues[i + 1];
if (!(key instanceof String) && !(key instanceof T)) {
throw Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices();
}
if (val == null) {
if (T.label.equals(key)) {
throw Element.Exceptions.labelCanNotBeNull();
}
// Ignore null value for tinkerpop test compatibility
continue;
}
if (key.equals(T.id)) {
elemKeys.id = val;
} else if (key.equals(T.label)) {
elemKeys.label = val;
} else {
elemKeys.keys.add(key.toString());
}
}
return elemKeys;
}
public static final Id getIdValue(HugeType type, Object idValue) {
assert type.isGraph();
Id id = getIdValue(idValue);
if (type.isVertex()) {
return id;
} else {
if (id == null || id instanceof EdgeId) {
return id;
}
return EdgeId.parse(id.asString());
}
}
@Watched(prefix = "element")
protected static Id getIdValue(Object idValue) {
if (idValue == null) {
return null;
}
if (idValue instanceof String) {
// String id
return IdGenerator.of((String) idValue);
} else if (idValue instanceof Number) {
// Long id
return IdGenerator.of(((Number) idValue).longValue());
} else if (idValue instanceof UUID) {
// UUID id
return IdGenerator.of((UUID) idValue);
} else if (idValue instanceof Id) {
// Id itself
return (Id) idValue;
} else if (idValue instanceof Element) {
// Element
return (Id) ((Element) idValue).id();
}
// Throw if error type
throw new UnsupportedOperationException(String.format(
"Invalid element id: %s(%s)",
idValue, idValue.getClass().getSimpleName()));
}
@Watched(prefix = "element")
public static final Object getLabelValue(Object... keyValues) {
Object labelValue = null;
for (int i = 0; i < keyValues.length; i = i + 2) {
if (keyValues[i].equals(T.label)) {
labelValue = keyValues[i + 1];
E.checkArgument(labelValue instanceof String ||
labelValue instanceof VertexLabel,
"Expect a string or a VertexLabel object " +
"as the vertex label argument, but got: '%s'",
labelValue);
if (labelValue instanceof String) {
ElementHelper.validateLabel((String) labelValue);
}
break;
}
}
return labelValue;
}
public static int intFromId(Id id) {
E.checkArgument(id instanceof IdGenerator.LongId,
"Can't get number from %s(%s)", id, id.getClass());
return ((IdGenerator.LongId) id).intValue();
}
public static final class ElementKeys {
private Object label = null;
private Object id = null;
private Set<String> keys = new HashSet<>();
public Object label() {
return this.label;
}
public void label(Object label) {
this.label = label;
}
public Object id() {
return this.id;
}
public void id(Object id) {
this.id = id;
}
public Set<String> keys() {
return this.keys;
}
public void keys(Set<String> keys) {
this.keys = keys;
}
}
}