/* * 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.nodes.compute.object; import com.google.common.base.Optional; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.logging.Logger; import org.sfs.Server; import org.sfs.SfsRequest; import org.sfs.VertxContext; import org.sfs.auth.Authenticate; import org.sfs.elasticsearch.object.LoadAccountAndContainerAndObject; import org.sfs.elasticsearch.object.RemoveObject; import org.sfs.elasticsearch.object.UpdateObject; import org.sfs.rx.ConnectionCloseTerminus; import org.sfs.rx.ToVoid; import org.sfs.validate.ValidateActionAuthenticated; import org.sfs.validate.ValidateActionObjectDelete; import org.sfs.validate.ValidateObjectPath; import org.sfs.validate.ValidateOptimisticObjectLock; import org.sfs.vo.PersistentObject; import org.sfs.vo.TransientVersion; import rx.Observable; import java.util.ArrayList; import java.util.List; import java.util.Set; import static com.google.common.base.Predicates.notNull; import static com.google.common.base.Splitter.on; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.FluentIterable.from; import static com.google.common.primitives.Longs.tryParse; import static io.vertx.core.logging.LoggerFactory.getLogger; import static java.lang.Boolean.TRUE; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.util.Calendar.getInstance; import static java.util.Collections.emptySet; import static org.sfs.rx.Defer.aVoid; import static org.sfs.rx.Defer.just; import static org.sfs.util.NullSafeAscii.equalsIgnoreCase; import static org.sfs.util.SfsHttpQueryParams.VERSION; import static org.sfs.vo.ObjectPath.fromSfsRequest; public class DeleteObject implements Handler<SfsRequest> { private static final Logger LOGGER = getLogger(DeleteObject.class); @Override public void handle(final SfsRequest httpServerRequest) { MultiMap queryParams = httpServerRequest.params(); final String versionAsString = queryParams.get(VERSION); VertxContext<Server> vertxContext = httpServerRequest.vertxContext(); aVoid() .flatMap(new Authenticate(httpServerRequest)) .flatMap(new ValidateActionAuthenticated(httpServerRequest)) .map(aVoid -> fromSfsRequest(httpServerRequest)) .map(new ValidateObjectPath()) .flatMap(new LoadAccountAndContainerAndObject(vertxContext)) .flatMap(persistentObject -> { int maxRevisions = persistentObject.getParent().getMaxObjectRevisions(); boolean hasVersionString = !isNullOrEmpty(versionAsString); // if there is no version number supplied // add a delete marker if the newest version // isn't already a delete marker List<TransientVersion> versionsToCheck = new ArrayList<>(); TransientVersion newVersion = null; if (!hasVersionString) { if (maxRevisions <= 0) { for (TransientVersion transientVersion : persistentObject.getVersions()) { transientVersion.setDeleted(TRUE); versionsToCheck.add(transientVersion); } } else { Optional<TransientVersion> oNewestVersion = persistentObject.getNewestVersion(); if (oNewestVersion.isPresent()) { TransientVersion newestVersion = oNewestVersion.get(); versionsToCheck.add(newestVersion); if (!TRUE.equals(newestVersion.getDeleteMarker())) { newVersion = persistentObject.newVersion() .merge(newestVersion.toJsonObject()) .setDeleteMarker(TRUE); } } } } else { final boolean all = equalsIgnoreCase("all", versionAsString); final Set<Long> toDeleteVersions; if (!all) { toDeleteVersions = from(on(',').omitEmptyStrings().trimResults().split(versionAsString)) .transform(input -> tryParse(input)) .filter(notNull()) .toSet(); } else { toDeleteVersions = emptySet(); } Iterable<TransientVersion> versionsToDelete = from(persistentObject.getVersions()) .filter(notNull()) .filter(input -> all || toDeleteVersions.contains(input.getId())); for (TransientVersion version : versionsToDelete) { version.setDeleted(TRUE); versionsToCheck.add(version); } } TransientVersion finalNewVersion = newVersion; return Observable.from(versionsToCheck) .flatMap(new ValidateActionObjectDelete(httpServerRequest)) .count() .map(new ToVoid<>()) .flatMap(aVoid -> { if (finalNewVersion != null) { return new PruneObject(httpServerRequest.vertxContext(), finalNewVersion).call(persistentObject) .map(modified -> persistentObject); } else { return new PruneObject(httpServerRequest.vertxContext()).call(persistentObject) .map(modified -> persistentObject); } }); }) .flatMap(persistentObject -> { if (persistentObject.getVersions().isEmpty()) { return just(persistentObject) .flatMap(new RemoveObject(httpServerRequest.vertxContext())) .map(new ValidateOptimisticObjectLock()); } else { return just(persistentObject) .map(persistentObject1 -> persistentObject.setUpdateTs(getInstance())) .flatMap(new UpdateObject(httpServerRequest.vertxContext())) .map(new ValidateOptimisticObjectLock()); } }) .single() .subscribe(new ConnectionCloseTerminus<PersistentObject>(httpServerRequest) { @Override public void onNext(PersistentObject input) { httpServerRequest.response().setStatusCode(HTTP_NO_CONTENT); } }); } }