public class ServiceInstanceDoesNotExistException extends RuntimeException {
	
	private static final long serialVersionUID = -1879753092397657116L;
	
	public ServiceInstanceDoesNotExistException(String serviceInstanceId) {
		super("Service instance does not exist: id=" + serviceInstanceId);
	}

} public class ServiceInstanceExistsException extends RuntimeException {

	private static final long serialVersionUID = -914571358227517785L;
	
	public ServiceInstanceExistsException(String serviceInstanceId, String serviceDefinitionId) {
		super("Service instance with the given ID already exists: " +
				"serviceInstanceId=" + serviceInstanceId +
				", serviceDefinitionId=" + serviceDefinitionId);
	}

} public class ServiceInstanceUpdateNotSupportedException extends RuntimeException {

	private static final long serialVersionUID = 4719676639792071582L;

	public ServiceInstanceUpdateNotSupportedException(String message) {
		super(message);
	}

	public ServiceInstanceUpdateNotSupportedException(String message, Throwable cause) {
		super(message, cause);
	}

	public ServiceInstanceUpdateNotSupportedException(Throwable cause) {
		super(cause);
	}

} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
public class CreateServiceInstanceRequest extends AsyncParameterizedServiceInstanceRequest {

	/**
	 * The ID of the service to provision, from the broker catalog.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("service_id")
	private final String serviceDefinitionId;

	/**
	 * The ID of the plan to provision within the service, from the broker catalog.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("plan_id")
	private final String planId;

	/**
	 * The Cloud Controller GUID of the organization under which the service is to be provisioned.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("organization_guid")
	private final String organizationGuid;

	/**
	 * The Cloud Controller GUID of the space under which the service is to be provisioned.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("space_guid")
	private final String spaceGuid;

	/**
	 * The Cloud Controller GUID of the service instance to provision. This ID will be used for future
	 * requests for the same service instance (e.g. bind and deprovision), so the broker must use it to
	 * correlate any resource it creates.
	 */
	@JsonIgnore
	private transient String serviceInstanceId;

	/**
	 * The {@link ServiceDefinition} of the service to provision. This is resolved from the
	 * <code>serviceDefinitionId</code> as a convenience to the broker.
	 */
	@JsonIgnore
	private transient ServiceDefinition serviceDefinition;

	public CreateServiceInstanceRequest() {
		super(null);
		this.serviceDefinitionId = null;
		this.planId = null;
		this.organizationGuid = null;
		this.spaceGuid = null;
	}
	
	public CreateServiceInstanceRequest(String serviceDefinitionId, String planId,
										String organizationGuid, String spaceGuid,
										Map<String, Object> parameters) {
		super(parameters);
		this.serviceDefinitionId = serviceDefinitionId;
		this.planId = planId;
		this.organizationGuid = organizationGuid;
		this.spaceGuid = spaceGuid;
	}

	public CreateServiceInstanceRequest(String serviceDefinitionId, String planId,
										String organizationGuid, String spaceGuid) {
		this(serviceDefinitionId, planId, organizationGuid, spaceGuid, null);
	}

	public CreateServiceInstanceRequest withServiceDefinition(ServiceDefinition serviceDefinition) {
		this.serviceDefinition = serviceDefinition;
		return this;
	}

	public CreateServiceInstanceRequest withServiceInstanceId(final String serviceInstanceId) {
		this.serviceInstanceId = serviceInstanceId;
		return this;
	}

	public CreateServiceInstanceRequest withAsyncAccepted(boolean asyncAccepted) {
		this.asyncAccepted = asyncAccepted;
		return this;
	}
} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
public class CreateServiceInstanceResponse extends AsyncServiceInstanceResponse {
	/**
	 * The URL of a web-based management user interface for the service instance. Can be <code>null</code> to indicate
	 * that a management dashboard is not provided.
	 */
	@JsonSerialize
	@JsonProperty("dashboard_url")
	private String dashboardUrl;

	public CreateServiceInstanceResponse(String dashboardUrl, boolean async) {
		super(async);
		this.dashboardUrl = dashboardUrl;
	}

	public CreateServiceInstanceResponse() {
		this(null, false);
	}

	public CreateServiceInstanceResponse(boolean async) {
		this(null, async);
	}

	public CreateServiceInstanceResponse(String dashboardUrl) {
		this(dashboardUrl, false);
	}
} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeleteServiceInstanceRequest extends AsyncServiceInstanceRequest {
	/**
	 * The Cloud Controller GUID of the service instance to deprovision.
	 */
	private final String serviceInstanceId;

	/**
	 * The ID of the service to deprovision, from the broker catalog.
	 */
	private final String serviceDefinitionId;

	/**
	 * The ID of the plan to deprovision within the service, from the broker catalog.
	 */
	private final String planId;

	/**
	 * The {@link ServiceDefinition} of the service to deprovision. This is resolved from the
	 * <code>serviceDefinitionId</code> as a convenience to the broker.
	 */
	private transient final ServiceDefinition serviceDefinition;

	public DeleteServiceInstanceRequest(String instanceId, String serviceId,
										String planId, ServiceDefinition serviceDefinition,
										boolean asyncAccepted) {
		this.serviceInstanceId = instanceId;
		this.serviceDefinitionId = serviceId;
		this.planId = planId;
		this.serviceDefinition = serviceDefinition;
		this.asyncAccepted = asyncAccepted;
	}

	public DeleteServiceInstanceRequest(String instanceId, String serviceId,
										String planId, ServiceDefinition serviceDefinition) {
		this(instanceId, serviceId, planId, serviceDefinition, false);
	}

	public DeleteServiceInstanceRequest withAsyncAccepted(boolean asyncAccepted) {
		this.asyncAccepted = asyncAccepted;
		return this;
	}
} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeleteServiceInstanceResponse extends AsyncServiceInstanceResponse {
	public DeleteServiceInstanceResponse(boolean async) {
		super(async);
	}

	public DeleteServiceInstanceResponse() {
		this(false);
	}
} @Getter
@ToString
@EqualsAndHashCode
public class ErrorMessage {
	@JsonProperty("description")
	private final String message;

	public ErrorMessage(String message) {
		this.message = message;
	}
} @Getter
@ToString
@EqualsAndHashCode
public class GetLastServiceOperationRequest {
	/**
	 * The Cloud Controller GUID of the service instance to get the status of.
	 */
	private final String serviceInstanceId;

	public GetLastServiceOperationRequest(String serviceInstanceId) {
		this.serviceInstanceId = serviceInstanceId;
	}
} @Getter
@ToString
@EqualsAndHashCode
@JsonAutoDetect
public class GetLastServiceOperationResponse {
	/**
	 * The current state of the asynchronous request.
	 */
	private final OperationState state;

	/**
	 * A user-facing message displayed to the Cloud Controller API client. Can be used to tell the user details
	 * about the status of the operation. Can be <code>null</code>.
	 */
	@JsonSerialize
	private final String description;

	/**
	 * Should be set to <code>true</code> in response to a request for the status of an asynchronous delete request,
	 * and <code>false</code> otherwise.
	 */
	private final boolean deleteOperation;

	public GetLastServiceOperationResponse(final OperationState operationState, final String description,
										   final boolean deleteOperation) {
		this.state = operationState;
		this.description = description;
		this.deleteOperation = deleteOperation;
	}
	
	public GetLastServiceOperationResponse(final OperationState operationState, final String description) {
		this(operationState, description, false);
	}

	public GetLastServiceOperationResponse(final OperationState operationState) {
		this(operationState, null, false);
	}

	@JsonSerialize
	@JsonProperty("state")
	public String getStateValue() {
		return state.getValue();
	}
} public enum OperationState {
	/**
	 * Indicates that a request is still being processed. Cloud Foundry will continue polling for the current state.
	 */
	IN_PROGRESS("in progress"),

	/**
	 * Indicates that a request completed successfully. Cloud Foundry will stop polling for the current state.
	 */
	SUCCEEDED("succeeded"),

	/**
	 * Indicates that a request completed unsuccessfully. Cloud Foundry will stop polling for the current state.
	 */
	FAILED("failed");

	private final String state;

	OperationState(String state) {
		this.state = state;
	}

	public String getValue() {
		return state;
	}
} @Getter
@ToString
@EqualsAndHashCode
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceDefinition {
	/**
	 * An identifier used to correlate this service in future requests to the catalog. This must be unique within
	 * a Cloud Foundry deployment. Using a GUID is recommended.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("id")
	private String id;

	/**
	 * A CLI-friendly name of the service that will appear in the catalog. The value should be all lowercase,
	 * with no spaces.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("name")
	private String name;

	/**
	 * A user-friendly short description of the service that will appear in the catalog.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("description")
	private String description;

	/**
	 * Indicates whether the service can be bound to applications.
	 */
	@JsonSerialize
	@JsonProperty("bindable")
	private boolean bindable;

	/**
	 * Indicates whether the service supports requests to update instances to use a different plan from the one
	 * used to provision a service instance.
	 */
	@JsonSerialize
	@JsonProperty("plan_updateable")
	private boolean planUpdateable;

	/**
	 * A list of plans for this service.
	 */
	@NotEmpty
	@JsonSerialize(nullsUsing = EmptyListSerializer.class)
	@JsonProperty("plans")
	private List<Plan> plans;

	/**
	 * A list of tags to aid in categorizing and classifying services with similar characteristics.
	 */
	@JsonSerialize(nullsUsing = EmptyListSerializer.class)
	@JsonProperty("tags")
	private List<String> tags;

	/**
	 * A map of metadata to further describe a service offering.
	 */
	@JsonSerialize(nullsUsing = EmptyMapSerializer.class)
	@JsonProperty("metadata")
	private Map<String, Object> metadata;

	/**
	 * A list of permissions that the user would have to give the service, if they provision it. See
	 * {@link ServiceDefinitionRequires} for supported permissions.
	 */
	@JsonSerialize(nullsUsing = EmptyListSerializer.class)
	@JsonProperty("requires")
	private List<String> requires;

	/**
	 * Data necessary to activate the Dashboard SSO feature for this service.
	 */
	@JsonSerialize
	@JsonProperty("dashboard_client")
	private DashboardClient dashboardClient;

	public ServiceDefinition() {
	}

	public ServiceDefinition(String id, String name, String description, boolean bindable, List<Plan> plans) {
		this.id = id;
		this.name = name;
		this.description = description;
		this.bindable = bindable;
		this.plans = plans;
	}

	public ServiceDefinition(String id, String name, String description, boolean bindable, boolean planUpdateable,
							 List<Plan> plans, List<String> tags, Map<String, Object> metadata, List<String> requires,
							 DashboardClient dashboardClient) {
		this(id, name, description, bindable, plans);
		this.tags = tags;
		this.metadata = metadata;
		this.requires = requires;
		this.planUpdateable = planUpdateable;
		this.dashboardClient = dashboardClient;
	}
} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
public class UpdateServiceInstanceRequest  extends AsyncParameterizedServiceInstanceRequest {

	/**
	 * The ID of the service to update, from the broker catalog.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("service_id")
	private final String serviceDefinitionId;

	/**
	 * The ID of the plan to update within the service, from the broker catalog.
	 */
	@NotEmpty
	@JsonSerialize
	@JsonProperty("plan_id")
	private final String planId;

	/**
	 * The Cloud Controller GUID of the service instance to update.
	 */
	@JsonIgnore
	private transient String serviceInstanceId;

	/**
	 * The {@link ServiceDefinition} of the service to update. This is resolved from the
	 * <code>serviceDefinitionId</code> as a convenience to the broker.
	 */
	@JsonIgnore
	private transient ServiceDefinition serviceDefinition;

	public UpdateServiceInstanceRequest() {
		super(null);
		this.serviceDefinitionId = null;
		this.planId = null;
	}
	
	public UpdateServiceInstanceRequest(String serviceDefinitionId, String planId,
										Map<String, Object> parameters) {
		super(parameters);
		this.serviceDefinitionId = serviceDefinitionId;
		this.planId = planId;
	}

	public UpdateServiceInstanceRequest(String serviceDefinitionId, String planId) {
		this(serviceDefinitionId, planId, null);
	}

	public UpdateServiceInstanceRequest withServiceInstanceId(String serviceInstanceId) {
		this.serviceInstanceId = serviceInstanceId;
		return this;
	}

	public UpdateServiceInstanceRequest withServiceDefinition(ServiceDefinition serviceDefinition) {
		this.serviceDefinition = serviceDefinition;
		return this;
	}

	public UpdateServiceInstanceRequest withAsyncAccepted(boolean asyncAccepted) {
		this.asyncAccepted = asyncAccepted;
		return this;
	}
} @Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UpdateServiceInstanceResponse extends AsyncServiceInstanceResponse {
	public UpdateServiceInstanceResponse(boolean async) {
		super(async);
	}

	public UpdateServiceInstanceResponse() {
		this(false);
	}
} public interface CatalogService {

	/**
	 * Return the catalog of services provided by the service broker.
	 *
	 * @return the catalog of services
	 */
	Catalog getCatalog();

	/**
	 * Get a service definition from the catalog by ID.
	 *
	 * @param serviceId  The ID of the service definition in the catalog
	 * @return the service definition, or null if it doesn't exist
	 */
	ServiceDefinition getServiceDefinition(String serviceId);
	
} public interface ServiceInstanceService {

	/**
	 * Create (provision) a new service instance.
	 *
	 * @param request containing the details of the request
	 * @return the details of the completed request
	 * @throws ServiceInstanceExistsException if a service instance with the given ID is already known to the broker
	 * @throws ServiceBrokerAsyncRequiredException if the broker requires asynchronous processing of the request
	 */
	CreateServiceInstanceResponse createServiceInstance(CreateServiceInstanceRequest request);

	/**
	 * Get the status of the last requested operation for a service instance.
	 *
	 * @param request containing the details of the request
	 * @return the details of the completed request
	 * @throws ServiceInstanceDoesNotExistException if a service instance with the given ID is not known to the broker
	 */
	GetLastServiceOperationResponse getLastOperation(GetLastServiceOperationRequest request);

	/**
	 * Delete (deprovision) a service instance.
	 *
	 * @param request containing the details of the request
	 * @return the details of the completed request
	 * @throws ServiceInstanceDoesNotExistException if a service instance with the given ID is not known to the broker
	 * @throws ServiceBrokerAsyncRequiredException if the broker requires asynchronous processing of the request
	 */
	DeleteServiceInstanceResponse deleteServiceInstance(DeleteServiceInstanceRequest request);

	/**
	 * Update a service instance. Only modification of the service plan is supported.
	 *
	 * @param request containing the details of the request
	 * @return the details of the completed request
	 * @throws ServiceInstanceUpdateNotSupportedException if particular plan change is not supported
	 *         or if the request can not currently be fulfilled due to the state of the instance
	 * @throws ServiceInstanceDoesNotExistException if a service instance with the given ID is not known to the broker
	 * @throws ServiceBrokerAsyncRequiredException if the broker requires asynchronous processing of the request
	 */
	UpdateServiceInstanceResponse updateServiceInstance(UpdateServiceInstanceRequest request);
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.servicebroker.exception.ServiceInstanceDoesNotExistException;
import org.springframework.cloud.servicebroker.exception.ServiceInstanceExistsException;
import org.springframework.cloud.servicebroker.exception.ServiceInstanceUpdateNotSupportedException;
import org.springframework.cloud.servicebroker.model.CreateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.CreateServiceInstanceResponse;
import org.springframework.cloud.servicebroker.model.DeleteServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.DeleteServiceInstanceResponse;
import org.springframework.cloud.servicebroker.model.ErrorMessage;
import org.springframework.cloud.servicebroker.model.GetLastServiceOperationRequest;
import org.springframework.cloud.servicebroker.model.GetLastServiceOperationResponse;
import org.springframework.cloud.servicebroker.model.OperationState;
import org.springframework.cloud.servicebroker.model.ServiceDefinition;
import org.springframework.cloud.servicebroker.model.UpdateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.UpdateServiceInstanceResponse;
import org.springframework.cloud.servicebroker.service.CatalogService;
import org.springframework.cloud.servicebroker.service.ServiceInstanceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
package org.springframework.cloud.servicebroker.controller;



/**
 * See: http://docs.cloudfoundry.org/services/api.html
 * 
 * @author sgreenberg@pivotal.io
 */
@RestController
@RequestMapping("/v2/service_instances/{instanceId}")
@Slf4j
public class ServiceInstanceController extends BaseController {

	private ServiceInstanceService service;

	@Autowired
 	public ServiceInstanceController(CatalogService catalogService, ServiceInstanceService serviceInstanceService) {
		super(catalogService);
		this.service = serviceInstanceService;
	}

	@RequestMapping(method = RequestMethod.PUT)
	public ResponseEntity<?> createServiceInstance(@PathVariable("instanceId") String serviceInstanceId,
												   @Valid @RequestBody CreateServiceInstanceRequest request,
												   @RequestParam(value = "accepts_incomplete", required = false) boolean acceptsIncomplete) {
		log.debug("Creating a service instance: serviceInstanceId=" + serviceInstanceId);

		ServiceDefinition serviceDefinition = getRequiredServiceDefinition(request.getServiceDefinitionId());

		request.withServiceInstanceId(serviceInstanceId)
				.withServiceDefinition(serviceDefinition)
				.withAsyncAccepted(acceptsIncomplete);

		CreateServiceInstanceResponse response = service.createServiceInstance(request);

		log.debug("Creating a service instance succeeded: serviceInstanceId=" + serviceInstanceId);

		return new ResponseEntity<>(response, response.isAsync() ? HttpStatus.ACCEPTED : HttpStatus.CREATED);
	}

	@RequestMapping(value = "/last_operation", method = RequestMethod.GET)
	public ResponseEntity<?> getServiceInstanceLastOperation(@PathVariable("instanceId") String serviceInstanceId) {

		log.debug("Getting service instance status: serviceInstanceId=" + serviceInstanceId);

		GetLastServiceOperationRequest request = new GetLastServiceOperationRequest(serviceInstanceId);

		GetLastServiceOperationResponse response = service.getLastOperation(request);

		log.debug("Getting service instance status succeeded: serviceInstanceId=" + serviceInstanceId
				+ ", state=" + response.getState()
				+ ", description=" + response.getDescription());

		boolean isSuccessfulDelete = response.getState().equals(OperationState.SUCCEEDED) && response.isDeleteOperation();

		return new ResponseEntity<>(response, isSuccessfulDelete ? HttpStatus.GONE : HttpStatus.OK);
	}

	@RequestMapping(method = RequestMethod.DELETE)
	public ResponseEntity<?> deleteServiceInstance(@PathVariable("instanceId") String serviceInstanceId,
												   @RequestParam("service_id") String serviceDefinitionId,
												   @RequestParam("plan_id") String planId,
												   @RequestParam(value = "async", required = false) boolean acceptsIncomplete) {
		log.debug("Deleting a service instance: "
				+ "serviceInstanceId=" + serviceInstanceId
				+ ", serviceDefinitionId=" + serviceDefinitionId
				+ ", planId=" + planId
				+ ", acceptsIncomplete=" + acceptsIncomplete);

		try {
			DeleteServiceInstanceRequest request =
					new DeleteServiceInstanceRequest(serviceInstanceId, serviceDefinitionId, planId,
							getServiceDefinition(serviceDefinitionId), acceptsIncomplete);

			DeleteServiceInstanceResponse response = service.deleteServiceInstance(request);

			log.debug("Deleting a service instance succeeded: "
					+ "serviceInstanceId=" + serviceInstanceId);

			return new ResponseEntity<>("{}", response.isAsync() ? HttpStatus.ACCEPTED : HttpStatus.OK);
		} catch (ServiceInstanceDoesNotExistException e) {