/* * Copyright 2016 The Simple File Server Authors * * Licensed 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.sfs.vo; import com.google.common.base.Optional; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import org.sfs.util.IdentityComparator; import java.util.Calendar; import java.util.NavigableSet; import java.util.TreeSet; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Optional.of; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.addAll; import static com.google.common.collect.Ordering.from; import static com.google.common.math.LongMath.checkedAdd; import static java.util.Calendar.getInstance; import static org.sfs.util.DateFormatter.fromDateTimeString; import static org.sfs.util.DateFormatter.toDateTimeString; public abstract class XObject<T extends XObject> { private final PersistentContainer parent; private final String id; private Calendar createTs; private Calendar updateTs; private String ownerGuid; // id of data node that scans and manages this object. This value // allows the work of managing objects to be split over the nodes // without using a consistent hash ring for object assignment private String nodeId; // largest id numbers should be at the beginning of the collection private NavigableSet<TransientVersion> versions = new TreeSet<>(from(new IdentityComparator())); public XObject(PersistentContainer parent, String id) { this.parent = parent; this.id = id; } public Calendar getCreateTs() { if (createTs == null) createTs = getInstance(); return createTs; } public T setCreateTs(Calendar createTs) { this.createTs = createTs; return (T) this; } public Calendar getUpdateTs() { if (updateTs == null) updateTs = getInstance(); return updateTs; } public T setUpdateTs(Calendar updateTs) { this.updateTs = updateTs; return (T) this; } public Optional<String> getOwnerGuid() { return fromNullable(ownerGuid); } public T setOwnerGuid(String ownerGuid) { this.ownerGuid = ownerGuid; return (T) this; } public Optional<String> getNodeId() { return fromNullable(nodeId); } public T setNodeId(String nodeId) { this.nodeId = nodeId; return (T) this; } public PersistentContainer getParent() { return parent; } public String getId() { return id; } public Optional<TransientVersion> getNewestVersion() { if (!versions.isEmpty()) { return of(versions.last()); } else { return absent(); } } public Optional<TransientVersion> getOldestVersion() { if (!versions.isEmpty()) { return of(versions.first()); } else { return absent(); } } public Optional<TransientVersion> getVersion(long id) { for (TransientVersion version : versions) { if (id == version.getId()) { return of(version); } } return absent(); } public NavigableSet<TransientVersion> getVersions() { return versions; } public T setVersions(Iterable<TransientVersion> versions) { this.versions.clear(); addAll(this.versions, versions); return (T) this; } public TransientVersion newVersion() { if (this.versions.isEmpty()) { TransientVersion newVersion = new TransientVersion(this, 0L); this.versions.add(newVersion); return newVersion; } else { TransientVersion existingNewest = getNewestVersion().get(); TransientVersion newVersion = new TransientVersion( this, checkedAdd(existingNewest.getId(), 1)); newVersion = newVersion.setCreateTs(existingNewest.getCreateTs()); this.versions.add(newVersion); return newVersion; } } public T merge(JsonObject document) { this.versions.clear(); this.ownerGuid = document.getString("owner_guid"); this.nodeId = document.getString("node_id"); JsonArray jsonVerionArray = document.getJsonArray("versions"); if (jsonVerionArray != null) { for (Object o : jsonVerionArray) { JsonObject versionDocument = (JsonObject) o; Long id = versionDocument.getLong("id"); if (id == null) { checkNotNull(id, "id is null for %s", document.encodePrettily()); } TransientVersion transientVersion = new TransientVersion(this, id) .merge(versionDocument); versions.add(transientVersion); } } String createTimestamp = document.getString("create_ts"); String updateTimestamp = document.getString("update_ts"); if (createTimestamp != null) { setCreateTs(fromDateTimeString(createTimestamp)); } if (updateTimestamp != null) { setUpdateTs(fromDateTimeString(updateTimestamp)); } return (T) this; } public JsonObject toJsonObject() { JsonObject document = new JsonObject(); checkState(parent != null, "container cannot be null"); checkState(parent.getParent() != null, "account cannot be null"); document.put("account_id", parent.getParent().getId()); document.put("container_id", parent.getId()); document.put("node_id", nodeId); document.put("owner_guid", ownerGuid); JsonArray versionsJsonArray = new JsonArray(); for (TransientVersion transientVersion : versions) { versionsJsonArray.add(transientVersion.toJsonObject()); } document.put("versions", versionsJsonArray); document.put("version_count", versions.size()); Optional<TransientVersion> oOldestVersion = getOldestVersion(); if (oOldestVersion.isPresent()) { document.put("oldest_version_ts", toDateTimeString(oOldestVersion.get().getCreateTs())); } else { document.put("oldest_version_ts", (String) null); } document.put("create_ts", toDateTimeString(getCreateTs())); document.put("update_ts", toDateTimeString(getUpdateTs())); return document; } }