package net.ion.craken.node.crud; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Array; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.ion.craken.loaders.EntryKey; import net.ion.craken.node.ReadNode; import net.ion.craken.node.ReadSession; import net.ion.craken.node.WriteNode; import net.ion.craken.node.WriteSession; import net.ion.craken.node.convert.Functions; import net.ion.craken.node.crud.tree.ExtendPropertyId; import net.ion.craken.node.crud.tree.Fqn; import net.ion.craken.node.crud.tree.TreeNode; import net.ion.craken.node.crud.tree.impl.GridBlob; import net.ion.craken.node.crud.tree.impl.PropertyId; import net.ion.craken.node.crud.tree.impl.PropertyId.PType; import net.ion.craken.node.crud.tree.impl.PropertyValue; import net.ion.craken.node.crud.tree.impl.PropertyValue.VType; import net.ion.craken.node.exception.NodeIOException; import net.ion.framework.parse.gson.JsonElement; import net.ion.framework.parse.gson.JsonObject; import net.ion.framework.util.ArrayUtil; import net.ion.framework.util.IOUtil; import net.ion.framework.util.MapUtil; import net.ion.framework.util.ObjectUtil; import net.ion.framework.util.SetUtil; import net.ion.framework.util.StringUtil; import net.ion.nsearcher.config.Central; import net.ion.nsearcher.search.filter.TermFilter; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.Term; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.TermQuery; import org.infinispan.io.GridFilesystem; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Sets; public class WriteNodeImpl implements WriteNode { private WriteSession wsession; private Fqn fqn; public enum Touch implements PropertyValue.ReplaceValue<String> { MODIFY, REMOVE, REMOVECHILDREN, TOUCH; @Override public String replaceValue() { return this.toString(); } public VType vtype() { return VType.STR; } } private WriteNodeImpl(WriteSession wsession, Fqn fqn) { this.wsession = wsession; this.fqn = fqn; } public static WriteNode loadTo(WriteSession wsession, Fqn fqn) { // by pathBy return loadTo(wsession, fqn, Touch.TOUCH); } public static WriteNode loadTo(WriteSession wsession, Fqn fqn, Touch touch) { // by pathBy final WriteNodeImpl result = new WriteNodeImpl(wsession, fqn); wsession.notifyTouch(result, result.fqn(), touch, MapUtil.EMPTY); return result; } public WriteNode load(WriteSession wsession, Fqn fqn) { final WriteNodeImpl result = new WriteNodeImpl(wsession, fqn); wsession.notifyTouch(result, result.fqn(), Touch.TOUCH, MapUtil.EMPTY); return result; } public TreeNode<PropertyId, PropertyValue> tree() { TreeNode read = wsession.workspace().writeNode(fqn); if (read == null){ wsession.workspace().createNode(wsession, SetUtil.EMPTY, fqn) ; } return read; } public WriteSession session() { return wsession; } private PropertyId createNormalId(String key) { return PropertyId.normal(key); } private PropertyId createReferId(String key) { return PropertyId.refer(key); } public ReadNode toReadNode() { return new ReadNodeImpl(wsession.readSession(), fqn); } public String asString(String pid) { return property(pid).asString() ; } public <R> R defaultValue(String pid, R val) { return property(pid).defaultValue(val) ; } public String asDateFmt(String pid, String fmt) { return property(pid).asDateFmt(fmt) ; } public WriteNode asRef(String pid, Fqn prefix) { return wsession.pathBy(Fqn.fromRelativeElements(prefix, asString(pid))) ; } public WriteNode property(String key, Object value) { if (value instanceof PropertyValue) { return property(createNormalId(key), (PropertyValue) value); } else if (value != null && value.getClass().isArray()) { int length = Array.getLength(value); Set set = SetUtil.newOrdereddSet(); for (int i = 0; i < length; i++) { set.add(Array.get(value, i)); } return property(createNormalId(key), PropertyValue.createPrimitive(set)); } return property(createNormalId(key), PropertyValue.createPrimitive(value)); } public WriteNode encrypt(String key, String value) throws IOException { property(key, readSession().encrypt(value)) ; return this; } public WriteNode property(PropertyId pid, PropertyValue pvalue) { touch(Touch.MODIFY); tree().put(pid, pvalue); return this; } public WriteNode propertyIfAbsent(String key, Object value) { touch(Touch.MODIFY); tree().putIfAbsent(createNormalId(key), PropertyValue.createPrimitive(value)); return this; } public PropertyValue propertyIfAbsentEnd(String key, Object value) { touch(Touch.MODIFY); return ObjectUtil.coalesce(tree().putIfAbsent(createNormalId(key), PropertyValue.createPrimitive(value)), PropertyValue.NotFound); } public PropertyValue replace(String key, Object value) { touch(Touch.MODIFY); return ObjectUtil.coalesce(tree().replace(createNormalId(key), PropertyValue.createPrimitive(value)), PropertyValue.NotFound); } public boolean replace(String key, Object oldValue, Object newValue) { touch(Touch.MODIFY); return tree().replace(createNormalId(key), PropertyValue.createPrimitive(oldValue), PropertyValue.createPrimitive(newValue)); } public WriteNode append(String key, Object value) { return append(key, new Object[] { value }); } public WriteNode append(String key, Object... value) { touch(Touch.MODIFY); PropertyValue findValue = property(key); if (findValue == PropertyValue.NotFound) findValue = PropertyValue.createBlank(); findValue.append(value); tree().put(createNormalId(key), findValue); return this; } @Override public WriteNode unset(String key, Object value) { return unset(key, new Object[] { value }); } @Override public WriteNode refTos(String refName, String fqn) { return refTos(refName, new String[] { fqn }); } @Override public WriteNode unRefTos(String refName, String fqn) { return unRefTos(refName, new String[] { fqn }); } public WriteNode unset(String key, Object... values) { touch(Touch.MODIFY); final PropertyId propId = createNormalId(key); PropertyValue pvalue = tree().remove(propId); if (pvalue == null) return this; if (values != null && values.length > 0) { pvalue.remove(values); property(propId, pvalue); } if (pvalue.isBlob()) { File file = gfs().getFile(pvalue.asBlob().path()); if (file.exists()) file.delete() ; } return this; } public WriteNode clear() { touch(Touch.MODIFY); removeBlobIfExist(); tree().clearData(); return this; } private void removeBlobIfExist() { for (PropertyId pid : keys()) { PropertyValue pvalue = propertyId(pid); if (pvalue.isBlob()) { File file = gfs().getFile(pvalue.asBlob().path()); if (file.exists()) file.delete() ; } } } public WriteNode blob(String key, File file) { try { return blob(key, new FileInputStream(file)); } catch (FileNotFoundException e) { throw new NodeIOException(e); } } public WriteNode blob(String key, InputStream input) { try { final String path = fqn().toString() + "/" + key + ".dat"; PropertyValue gtvalue = GridBlob.create(wsession.workspace(), path).saveAt(input).asPropertyValue() ; property(createNormalId(key), gtvalue); } catch (IOException e) { throw new NodeIOException(e); } finally { IOUtil.closeSilent(input); } return this; } public WriteNode addChild(String relativeFqn) { return wsession.pathBy(Fqn.fromRelativeFqn(fqn(), Fqn.fromString(relativeFqn))); } public boolean removeSelf() { removeChildren() ; return parent().removeChild(fqn().name()); } public boolean removeChild(String childPath) { if (StringUtil.isBlank(childPath)) return false ; final Fqn target = Fqn.fromRelativeFqn(fqn(), Fqn.fromString(childPath)); // WriteNodeImpl found = (WriteNodeImpl) wsession.pathBy(target) ; // found.removeBlobIfExist(); boolean removed = tree().removeChild(target.name()); touchRemoved(Touch.REMOVE, target); return removed ; } public boolean removeChildren() { tree().removeChildren(); touchRemoved(Touch.REMOVECHILDREN, this.fqn()); return true ; } public boolean hasProperty(String pid) { return hasPropertyId(PropertyId.fromIdString(pid)); } public boolean hasPropertyId(PropertyId pid) { return keys().contains(pid); } public WriteNode ref(String refName) { // PropertyValue findProp = propertyId(PropertyId.refer(refName)) ; // if (findProp == PropertyValue.NotFound) throw new IllegalArgumentException("not found ref :" + refName) ; // return session().pathBy(Fqn.fromString(findProp.stringValue())); PropertyId referId = createReferId(refName); if (hasPropertyId(referId)) { String refPath = propertyId(referId).stringValue(); if (StringUtil.isBlank(refPath)) throw new IllegalArgumentException("not found ref :" + refName); return wsession.pathBy(refPath); } else { throw new IllegalArgumentException("not found ref :" + refName); } } public NodeIteratorList<WriteNode> refs(String refName) { return NodeIteratorList.WriteRefs(this, refName) ; } public WriteChildren refChildren(String refName) { Set<Fqn> result = tree().getReferencesFqn(refName) ; return new WriteChildren(session(), fqn, result.iterator()); } public WriteNode fromJson(JsonObject json) { for (Entry<String, JsonElement> entry : json.entrySet()) { append(this, entry.getKey(), entry.getValue()); } return this; } private void append(WriteNode that, String propId, JsonElement json) { if (json.isJsonNull()) { return; } else if (json.isJsonPrimitive()) { if (propId.startsWith("@")) { that.refTos(propId.substring(1), json.getAsJsonPrimitive().getAsString()); } else { that.append(propId, json.getAsJsonPrimitive().getValue()); } } else if (json.isJsonArray()) { for (JsonElement jele : json.getAsJsonArray().toArray()) { append(that, propId, jele); } } else if (json.isJsonObject()) { for (Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) { append(that.child(propId), entry.getKey(), entry.getValue()); } } } public WriteNode refTo(String refName, String fqn) { PropertyId referId = createReferId(refName); if (StringUtil.isBlank(fqn)) tree().remove(referId); else tree().put(referId, PropertyValue.createPrimitive(fqn)); if (!refName.startsWith("__")) touch(Touch.MODIFY); return this; } private GridFilesystem gfs() { return wsession.workspace().gfs(); } public WriteNode refTos(String refName, String... fqns) { PropertyId referId = createReferId(refName); PropertyValue findValue = propertyId(referId); if (findValue == PropertyValue.NotFound) findValue = PropertyValue.createBlank(); for (String fqn : fqns) { findValue.append(fqn); } tree().put(referId, findValue); touch(Touch.MODIFY); return this; } public WriteNode unRefTos(String refName, String... fqns) { PropertyId referId = createReferId(refName); PropertyValue findValue = propertyId(referId); if (fqns == null || fqns.length == 0 || findValue == PropertyValue.NotFound) { tree().remove(referId); } else { Set<String> removedRefs = tree().remove(referId).asSet(); for (String ref : removedRefs) { if (!ArrayUtil.contains(fqns, ref)) refTos(refName, ref); } } touch(Touch.MODIFY); return this; } // common public Fqn fqn() { return fqn; } public int dataSize() { return tree().dataSize(); } public WriteNode parent() { return load(session(), fqn.getParent()); } public <T> T transformer(Function<WriteNode, T> function) { return function.apply(this); } public boolean hasChild(String fqn) { return tree().hasChild(Fqn.fromString(fqn)); } public WriteNode child(String fqn) { return wsession.pathBy(Fqn.fromRelativeFqn(this.fqn(), Fqn.fromString(fqn))); // return load(wsession(), tree().getChild(Fqn.fromString(fqn))) ; } public WriteNode root() { return wsession.root(); } public Set<String> childrenNames() { Set<String> set = SetUtil.orderedSet(SetUtil.newSet()); for (Object object : tree().getChildrenNames()) { set.add(ObjectUtil.toString(object)); } return set; } public Set<PropertyId> keys() { return tree().getKeys(); } public Set<PropertyId> normalKeys() { return Sets.filter(keys(), new Predicate<PropertyId>() { @Override public boolean apply(PropertyId pid) { return pid.type() == PType.NORMAL; } }); } public boolean hasRef(String refName) { return keys().contains(createReferId(refName)); } public boolean hasRef(String refName, Fqn fqn) { return propertyId(createReferId(refName)).asSet().contains(fqn.toString()); } public PropertyValue property(String key) { return propertyId(createNormalId(key)); } public PropertyValue increase(String key) { property(key, property(key).asLong(0) + 1); return property(key); } public PropertyValue extendProperty(String propPath) { return ExtendPropertyId.create(propPath).propValue(this); } public PropertyValue propertyId(PropertyId pid) { PropertyValue result = ObjectUtil.coalesce(tree().get(pid), PropertyValue.NotFound); result.workspace(wsession.workspace()); return result; } public Map<PropertyId, PropertyValue> toMap() { if (tree() == null) return MapUtil.EMPTY ; return tree().getData(); } public Object id() { return fqn(); } public String toString() { return this.getClass().getSimpleName() + "[fqn=" + fqn().toString() + ", " + fqn().dataKey().action() + "]"; } public WriteChildren children() { final Iterator<Fqn> iter = tree().getChildrenFqn().iterator(); return new WriteChildren(session(), fqn, iter); } private void touch(Touch touch) { session().notifyTouch(this, this.fqn(), touch, MapUtil.create(fqn().toString(), fqn())); } private void touchRemoved(Touch touch, Fqn target) { session().notifyTouch(this, target, touch, MapUtil.EMPTY); } private ReadSession readSession() { return wsession.readSession(); } public ChildQueryRequest childQuery(Query query) throws ParseException, IOException { return ChildQueryRequest.create(readSession(), readSession().newSearcher(), query); } public ChildQueryRequest childTermQuery(String name, String value, boolean includeDecentTree) throws IOException, ParseException { if (StringUtil.isBlank(name) || StringUtil.isBlank(value)) throw new ParseException(String.format("not defined name or value[%s:%s]", name, value)) ; final ChildQueryRequest result = ChildQueryRequest.create(readSession(), readSession().newSearcher(), new TermQuery(new Term(name, value))); if (includeDecentTree){ result.filter(new QueryWrapperFilter(this.fqn().childrenQuery())); } else { result.filter(new TermFilter(EntryKey.PARENT, this.fqn().toString())); } return result; } public ChildQueryRequest childQuery(String query) throws IOException, ParseException { if (StringUtil.isBlank(query)) return childQuery(new TermQuery(new Term(EntryKey.PARENT, this.fqn().toString()))); Central central = readSession().workspace().central(); Analyzer analyzer = central.searchConfig().queryAnalyzer(); final ChildQueryRequest result = ChildQueryRequest.create(readSession(), readSession().newSearcher(), central.searchConfig().parseQuery(central.indexConfig(), analyzer, query)); result.filter(new TermFilter(EntryKey.PARENT, this.fqn().toString())); return result; } public ChildQueryRequest childQuery(String query, boolean includeDecentTree) throws ParseException, IOException { if (!includeDecentTree) return childQuery(query); if (StringUtil.isBlank(query)) return childQuery(this.fqn().childrenQuery()); Analyzer analyzer = readSession().queryAnalyzer(); Central central = session().workspace().central(); final ChildQueryRequest result = ChildQueryRequest.create(readSession(), readSession().newSearcher(), central.searchConfig().parseQuery(central.indexConfig(), analyzer, query)); result.filter(new QueryWrapperFilter(this.fqn().childrenQuery())); return result; } public WriteNode reindex(boolean includeSub){ wsession.workspace().reindex(this, wsession.workspace().central().indexConfig().indexAnalyzer(), includeSub) ; return this ; } public WriteNode reindex(Analyzer anal, boolean includeSub){ wsession.workspace().reindex(this, anal, includeSub) ; return this ; } public WriteNode moveTo(String targetParent){ return moveTo(targetParent, 1) ; } public WriteNode moveTo(String targetParent, int ancestorDepth){ if (ancestorDepth < 1) throw new IllegalArgumentException("depth must be 1 higher") ; Fqn parent = fqn().getAncestor(fqn().size() - ancestorDepth) ; Fqn tparent = Fqn.fromString(targetParent) ; if (parent.equals(tparent)) return this; WalkReadChildren walkChildren = this.toReadNode().walkChildren().includeSelf(true) ; for(ReadNode rnode : walkChildren){ Fqn fqnForMove = rnode.fqn().replaceAncestor(parent, tparent) ; WriteNode movenode = wsession.pathBy(fqnForMove) ; for (PropertyId propId : rnode.keys()) { movenode.property(propId.idString(), rnode.propertyId(propId).asObject()) ; } } this.removeSelf() ; return session().pathBy(Fqn.fromRelativeElements(tparent, fqn().name())) ; } public WriteNode copyTo(String targetParent, boolean includeSelf){ Fqn parent = fqn().getParent() ; Fqn tparent = Fqn.fromString(targetParent) ; if (includeSelf) { WalkReadChildren walkChildren = this.toReadNode().walkChildren().includeSelf(true) ; for(ReadNode rnode : walkChildren){ Fqn fqnForCopy = rnode.fqn().replaceAncestor(parent, tparent) ; WriteNode copynode = wsession.pathBy(fqnForCopy) ; for (PropertyId propId : rnode.keys()) { copynode.property(propId.idString(), rnode.propertyId(propId).asObject()) ; } } } else { Fqn fqnForCopy = this.fqn.replaceAncestor(parent, tparent) ; WriteNode copynode = wsession.pathBy(fqnForCopy) ; for (PropertyId propId : keys()) { copynode.property(propId.idString(), propertyId(propId).asObject()) ; } } return session().pathBy(Fqn.fromRelativeElements(tparent, fqn().name())) ; } @Override public OutputStream output(String key) throws IOException { final String path = fqn().toString() + "/" + key + ".dat"; File pfile = gfs().getFile(path).getParentFile() ; if (! pfile.exists()) pfile.mkdirs() ; blob(key, new ByteArrayInputStream(new byte[0])) ; return GridBlob.create(wsession.workspace(), path).toOutputStream() ; } public void debugPrint(){ this.toReadNode().transformer(Functions.READ_DEBUGPRINT); } }