/*
* Copyright 2013 Jive Software, Inc
*
* 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 com.jivesoftware.os.amza.deployable;
import com.google.common.base.Splitter;
import com.jivesoftware.os.amza.api.partition.Consistency;
import com.jivesoftware.os.amza.api.partition.Durability;
import com.jivesoftware.os.amza.api.partition.PartitionName;
import com.jivesoftware.os.amza.api.partition.PartitionProperties;
import com.jivesoftware.os.amza.api.stream.RowType;
import com.jivesoftware.os.amza.berkeleydb.BerkeleyDBWALIndexProvider;
import com.jivesoftware.os.amza.service.AmzaPartitionUpdates;
import com.jivesoftware.os.amza.service.AmzaService;
import com.jivesoftware.os.amza.service.Partition;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import com.jivesoftware.os.routing.bird.shared.ResponseHelper;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Singleton
@Path("/amza")
public class AmzaEndpoints {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
private final AmzaService amzaService;
public AmzaEndpoints(@Context AmzaService amzaService) {
this.amzaService = amzaService;
}
@GET
@Consumes("application/json")
@Path("/set")
public Response set(@QueryParam("ring") @DefaultValue("default") String ring,
@QueryParam("indexClassName") @DefaultValue(BerkeleyDBWALIndexProvider.INDEX_CLASS_NAME) String indexClassName,
@QueryParam("partition") String partitionName,
@QueryParam("consistency") @DefaultValue("none") String consistency,
@QueryParam("requireConsistency") @DefaultValue("true") boolean requireConsistency,
@QueryParam("key") String key,
@QueryParam("value") String value) {
try {
Partition partition = createPartitionIfAbsent(ring, indexClassName, partitionName, Consistency.valueOf(consistency), requireConsistency);
String[] keys = key.split(",");
String[] values = value.split(",");
AmzaPartitionUpdates updates = new AmzaPartitionUpdates();
for (int i = 0; i < keys.length; i++) {
//TODO prefix
updates.set(keys[i].getBytes(StandardCharsets.UTF_8), values[i].getBytes(StandardCharsets.UTF_8), -1);
}
partition.commit(Consistency.valueOf(consistency), null, updates, 30000);
return Response.ok("ok", MediaType.TEXT_PLAIN).build();
} catch (Exception x) {
LOG.warn("Failed to set partition:" + partitionName + " key:" + key + " value:" + value, x);
return ResponseHelper.INSTANCE.errorResponse("Failed to set partition:" + partitionName + " key:" + key + " value:" + value, x);
}
}
@POST
@Consumes("application/json")
@Path("/multiSet/{partition}")
public Response multiSet(
@QueryParam("indexClassName") @DefaultValue(BerkeleyDBWALIndexProvider.INDEX_CLASS_NAME) String indexClassName,
@PathParam("partition") String partitionName,
@QueryParam("consistency") @DefaultValue("none") String consistency,
@QueryParam("requireConsistency") @DefaultValue("true") boolean requireConsistency,
Map<String, String> values) {
try {
Partition partition = createPartitionIfAbsent("default", indexClassName, partitionName, Consistency.valueOf(consistency), requireConsistency);
AmzaPartitionUpdates updates = new AmzaPartitionUpdates();
for (Map.Entry<String, String> entry : values.entrySet()) {
//TODO prefix
updates.set(entry.getKey().getBytes(StandardCharsets.UTF_8), entry.getValue().getBytes(StandardCharsets.UTF_8), -1);
}
partition.commit(Consistency.valueOf(consistency), null, updates, 30000);
return Response.ok("ok", MediaType.TEXT_PLAIN).build();
} catch (Exception x) {
LOG.warn("Failed to set partition:" + partitionName + " values:" + values, x);
return ResponseHelper.INSTANCE.errorResponse("Failed to set partition:" + partitionName + " values:" + values, x);
}
}
@POST
@Consumes("application/json")
@Path("/multiSet/{ring}/{partition}")
public Response multiSet(
@QueryParam("indexClassName") @DefaultValue(BerkeleyDBWALIndexProvider.INDEX_CLASS_NAME) String indexClassName,
@PathParam("partition") String partitionName,
@QueryParam("consistency") @DefaultValue("none") String consistency,
@QueryParam("requireConsistency") @DefaultValue("true") boolean requireConsistency,
@PathParam("ring") String ring,
Map<String, String> values) {
try {
Partition partition = createPartitionIfAbsent(ring, indexClassName, partitionName, Consistency.valueOf(consistency), requireConsistency);
AmzaPartitionUpdates updates = new AmzaPartitionUpdates();
for (Map.Entry<String, String> entry : values.entrySet()) {
//TODO prefix
updates.set(entry.getKey().getBytes(StandardCharsets.UTF_8), entry.getValue().getBytes(StandardCharsets.UTF_8), -1);
}
partition.commit(Consistency.valueOf(consistency), null, updates, 30000);
return Response.ok("ok", MediaType.TEXT_PLAIN).build();
} catch (Exception x) {
LOG.warn("Failed to set partition:" + partitionName + " values:" + values, x);
return ResponseHelper.INSTANCE.errorResponse("Failed to set partition:" + partitionName + " values:" + values, x);
}
}
@GET
@Consumes("application/json")
@Path("/get")
public Response get(@QueryParam("ring") @DefaultValue("default") String ring,
@QueryParam("indexClassName") @DefaultValue(BerkeleyDBWALIndexProvider.INDEX_CLASS_NAME) String indexClassName,
@QueryParam("partition") String partitionName,
@QueryParam("consistency") @DefaultValue("none") String consistency,
@QueryParam("requireConsistency") @DefaultValue("true") boolean requireConsistency,
@QueryParam("key") String key) {
try {
Partition partition = createPartitionIfAbsent(ring, indexClassName, partitionName, Consistency.valueOf(consistency), requireConsistency);
List<String> got = new ArrayList<>();
//TODO prefix
partition.get(Consistency.valueOf(consistency), null, true,
stream -> {
for (String s : Splitter.on(',').split(key)) {
if (!stream.stream(s.getBytes(StandardCharsets.UTF_8))) {
return false;
}
}
return true;
},
(_prefix, _key, value, timestamp, tombstoned, version) -> {
if (timestamp != -1 && !tombstoned) {
got.add(new String(value, StandardCharsets.UTF_8));
}
return true;
});
return ResponseHelper.INSTANCE.jsonResponse(got);
} catch (Exception x) {
LOG.warn("Failed to get partition:" + partitionName + " key:" + key, x);
return ResponseHelper.INSTANCE.errorResponse("Failed to get partition:" + partitionName + " key:" + key, x);
}
}
@GET
@Consumes("application/json")
@Path("/remove")
public Response remove(@QueryParam("ring") @DefaultValue("default") String ring,
@QueryParam("indexClassName") @DefaultValue(BerkeleyDBWALIndexProvider.INDEX_CLASS_NAME) String indexClassName,
@QueryParam("partition") String partitionName,
@QueryParam("consistency") @DefaultValue("none") String consistency,
@QueryParam("requireConsistency") @DefaultValue("true") boolean requireConsistency,
@QueryParam("key") String key) {
try {
Partition partition = createPartitionIfAbsent(ring, indexClassName, partitionName, Consistency.valueOf(consistency), requireConsistency);
AmzaPartitionUpdates updates = new AmzaPartitionUpdates();
//TODO prefix
updates.remove(key.getBytes(StandardCharsets.UTF_8), -1);
partition.commit(Consistency.valueOf(consistency), null, updates, 30000);
return Response.ok("removed " + key, MediaType.TEXT_PLAIN).build();
} catch (Exception x) {
LOG.warn("Failed to remove partition:" + partitionName + " key:" + key, x);
return ResponseHelper.INSTANCE.errorResponse("Failed to remove partition:" + partitionName + " key:" + key, x);
}
}
Partition createPartitionIfAbsent(String ringName, String indexClassName,
String simplePartitionName, Consistency consistency, boolean requireConsistency) throws Exception {
amzaService.getRingWriter().ensureMaximalRing(ringName.getBytes(StandardCharsets.UTF_8), 10_000); //TODO config
PartitionName partitionName = new PartitionName(false,
ringName.getBytes(StandardCharsets.UTF_8),
simplePartitionName.getBytes(StandardCharsets.UTF_8));
amzaService.createPartitionIfAbsent(partitionName,
new PartitionProperties(Durability.fsync_never,
0, 0, 0, 0, 0, 0, 0, 0,
false,
consistency,
requireConsistency,
true,
false,
RowType.primary,
indexClassName,
-1,
null,
-1,
-1));
long maxSleep = TimeUnit.SECONDS.toMillis(30); // TODO expose to config
amzaService.awaitOnline(partitionName, maxSleep);
return amzaService.getPartition(partitionName);
}
}