/* * Copyright 2014 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.sync.deployable.endpoints; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.jivesoftware.os.amza.api.AmzaInterner; import com.jivesoftware.os.amza.api.partition.PartitionName; import com.jivesoftware.os.amza.sync.api.AmzaSyncPartitionConfig; import com.jivesoftware.os.amza.sync.api.AmzaSyncPartitionTuple; import com.jivesoftware.os.amza.sync.api.AmzaSyncSenderConfig; import com.jivesoftware.os.amza.sync.api.AmzaSyncStatus; import com.jivesoftware.os.amza.sync.deployable.AmzaSyncPartitionConfigStorage; import com.jivesoftware.os.amza.sync.deployable.AmzaSyncSender; import com.jivesoftware.os.amza.sync.deployable.AmzaSyncSenderMap; import com.jivesoftware.os.amza.sync.deployable.AmzaSyncSenders; import com.jivesoftware.os.mlogger.core.MetricLogger; import com.jivesoftware.os.mlogger.core.MetricLoggerFactory; import com.jivesoftware.os.routing.bird.shared.ResponseHelper; import java.util.Map; import java.util.Map.Entry; import javax.inject.Singleton; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; /** * @author jonathan */ @Singleton @Path("/amza/sync") public class AmzaSyncEndpoints { private static final MetricLogger LOG = MetricLoggerFactory.getLogger(); private final AmzaSyncSenderMap configStorage; private final AmzaSyncPartitionConfigStorage partitionConfigStorage; private final AmzaSyncSenders syncSenders; private final AmzaInterner amzaInterner; private final ResponseHelper responseHelper = ResponseHelper.INSTANCE; public AmzaSyncEndpoints(@Context AmzaSyncSenderMap configStorage, @Context AmzaSyncPartitionConfigStorage partitionConfigStorage, @Context AmzaSyncSenders syncSenders, @Context AmzaInterner amzaInterner) { this.configStorage = configStorage; this.partitionConfigStorage = partitionConfigStorage; this.syncSenders = syncSenders; this.amzaInterner = amzaInterner; } @GET @Path("/syncspace/list") @Produces(MediaType.APPLICATION_JSON) public Response listNamesSpaces() { try { Map<String, AmzaSyncSenderConfig> all = configStorage.getAll(); return Response.ok(all).build(); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @POST @Path("/syncspace/add/{name}") @Produces(MediaType.APPLICATION_JSON) public Response addsyncspace(@PathParam("name") String name, AmzaSyncSenderConfig syncspaceConfig) { try { configStorage.multiPut(ImmutableMap.of(name, syncspaceConfig)); return responseHelper.jsonResponse("Success"); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @DELETE @Path("/syncspace/delete/{name}") @Produces(MediaType.APPLICATION_JSON) public Response deletesyncspace(@PathParam("name") String name) { try { configStorage.multiRemove(ImmutableList.of(name)); return responseHelper.jsonResponse("Success"); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @GET @Path("/list/{syncspaceName}") @Produces(MediaType.APPLICATION_JSON) public Response getSyncing(@PathParam("syncspaceName") String syncspaceName) { try { Map<AmzaSyncPartitionTuple, AmzaSyncPartitionConfig> all = partitionConfigStorage.getAll(syncspaceName); if (all != null && !all.isEmpty()) { Map<String, AmzaSyncPartitionConfig> map = Maps.newHashMap(); for (Entry<AmzaSyncPartitionTuple, AmzaSyncPartitionConfig> a : all.entrySet()) { map.put(AmzaSyncPartitionTuple.toKeyString(a.getKey()), a.getValue()); } return Response.ok(map).build(); } return Response.ok("{}").build(); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @GET @Path("/cursors/{syncspaceName}") @Produces(MediaType.APPLICATION_JSON) public Response getCursors(@PathParam("syncspaceName") String syncspaceName) { try { AmzaSyncSender sender = syncSenders.getSender(syncspaceName); Map<String, AmzaSyncStatus> map = Maps.newHashMap(); if (sender != null) { sender.streamCursors(null, null, (fromPartitionName, toPartitionName, timestamp, cursor) -> { map.put(AmzaSyncPartitionTuple.toKeyString(new AmzaSyncPartitionTuple(fromPartitionName, toPartitionName)), new AmzaSyncStatus(timestamp, cursor.maxTimestamp, cursor.maxVersion, cursor.exists, cursor.taking)); return true; }); } return Response.ok(map).build(); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @GET @Path("/partitionCursors/{syncspaceName}/{fromPartitionNameBase64}/{toPartitionNameBase64}") @Produces(MediaType.APPLICATION_JSON) public Response getPartitionCursors(@PathParam("syncspaceName") String syncspaceName, @PathParam("fromPartitionNameBase64") String fromPartitionNameBase64, @PathParam("toPartitionNameBase64") String toPartitionNameBase64) { try { PartitionName from = amzaInterner.internPartitionNameBase64(fromPartitionNameBase64); PartitionName to = amzaInterner.internPartitionNameBase64(toPartitionNameBase64); AmzaSyncSender sender = syncSenders.getSender(syncspaceName); Map<String, AmzaSyncStatus> map = Maps.newHashMap(); if (sender != null) { sender.streamCursors(from, to, (fromPartitionName, toPartitionName, timestamp, cursor) -> { map.put(AmzaSyncPartitionTuple.toKeyString(new AmzaSyncPartitionTuple(fromPartitionName, toPartitionName)), new AmzaSyncStatus(timestamp, cursor.maxTimestamp, cursor.maxVersion, cursor.exists, cursor.taking)); return true; }); } return Response.ok(map).build(); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @POST @Path("/add/{syncspaceName}/{fromPartitionNameBase64}/{toPartitionNameBase64}") @Produces(MediaType.APPLICATION_JSON) public Response post(@PathParam("syncspaceName") String syncspaceName, @PathParam("fromPartitionNameBase64") String fromPartitionNameBase64, @PathParam("toPartitionNameBase64") String toPartitionNameBase64, AmzaSyncPartitionConfig config) { try { PartitionName from = amzaInterner.internPartitionNameBase64(fromPartitionNameBase64); PartitionName to = amzaInterner.internPartitionNameBase64(toPartitionNameBase64); AmzaSyncSenderConfig amzaSyncSenderConfig = configStorage.get(syncspaceName); if (amzaSyncSenderConfig == null) { LOG.warn("Rejected add from:{} to:{} for unknown syncspace:{}", from, to, syncspaceName); return Response.status(Status.BAD_REQUEST).entity("Syncspace does not exist: " + syncspaceName).build(); } if (from.equals(to) && amzaSyncSenderConfig.loopback) { LOG.warn("Rejected self-referential add for:{} for syncspace:{}", from, syncspaceName); return Response.status(Status.BAD_REQUEST).entity("Loopback syncspace cannot be self-referential").build(); } partitionConfigStorage.multiPut(syncspaceName, ImmutableMap.of(new AmzaSyncPartitionTuple(from, to), config)); return responseHelper.jsonResponse("Success"); } catch (Exception e) { LOG.error("Failed to add.", e); return Response.serverError().build(); } } @DELETE @Path("/delete/{syncspaceName}/{fromPartitionNameBase64}/{toPartitionNameBase64}") @Produces(MediaType.APPLICATION_JSON) public Response delete(@PathParam("syncspaceName") String syncspaceName, @PathParam("fromPartitionNameBase64") String fromPartitionNameBase64, @PathParam("toPartitionNameBase64") String toPartitionNameBase64) { try { PartitionName from = amzaInterner.internPartitionNameBase64(fromPartitionNameBase64); PartitionName to = amzaInterner.internPartitionNameBase64(toPartitionNameBase64); partitionConfigStorage.multiRemove(syncspaceName, ImmutableList.of(new AmzaSyncPartitionTuple(from, to))); return responseHelper.jsonResponse("Success"); } catch (Exception e) { LOG.error("Failed to get.", e); return Response.serverError().build(); } } @POST @Path("/reset/{syncspaceName}/{partitionNameBase64}") @Produces(MediaType.APPLICATION_JSON) public Response postReset(@PathParam("syncspaceName") String syncspaceName, @PathParam("partitionNameBase64") String partitionNameBase64) { try { if (syncSenders != null) { PartitionName partitionName = amzaInterner.internPartitionNameBase64(partitionNameBase64); AmzaSyncSender sender = syncSenders.getSender(syncspaceName); boolean result = sender != null && sender.resetCursors(partitionName); return Response.ok(result).build(); } else { return Response.status(Status.SERVICE_UNAVAILABLE).entity("Sender is not enabled").build(); } } catch (Exception e) { LOG.error("Failed to reset.", e); return Response.serverError().build(); } } }