org.apache.nifi.web.api.SiteToSiteResource.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.web.api.SiteToSiteResource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.nifi.web.api;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.Authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.UserContextKeys;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.node.NodeWorkload;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.remote.HttpRemoteSiteListener;
import org.apache.nifi.remote.VersionNegotiator;
import org.apache.nifi.remote.client.http.TransportProtocolVersionNegotiator;
import org.apache.nifi.remote.exception.BadRequestException;
import org.apache.nifi.remote.protocol.http.HttpHeaders;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.api.dto.ControllerDTO;
import org.apache.nifi.web.api.dto.remote.PeerDTO;
import org.apache.nifi.web.api.entity.ControllerEntity;
import org.apache.nifi.web.api.entity.PeersEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.commons.lang3.StringUtils.isEmpty;

/**
 * RESTful endpoint for managing a SiteToSite connection.
 */
@Path("/site-to-site")
@Api(value = "/site-to-site", description = "Provide access to site to site with this NiFi")
public class SiteToSiteResource extends ApplicationResource {

    private static final Logger logger = LoggerFactory.getLogger(SiteToSiteResource.class);

    private NiFiServiceFacade serviceFacade;
    private ClusterCoordinator clusterCoordinator;
    private Authorizer authorizer;

    private final ResponseCreator responseCreator = new ResponseCreator();
    private final VersionNegotiator transportProtocolVersionNegotiator = new TransportProtocolVersionNegotiator(1);
    private final HttpRemoteSiteListener transactionManager;

    public SiteToSiteResource(final NiFiProperties nifiProperties) {
        transactionManager = HttpRemoteSiteListener.getInstance(nifiProperties);
    }

    /**
     * Authorizes access to Site To Site details.
     * <p>
     * Note: Protected for testing purposes
     */
    protected void authorizeSiteToSite() {
        final NiFiUser user = NiFiUserUtils.getNiFiUser();

        final Map<String, String> userContext;
        if (!StringUtils.isBlank(user.getClientAddress())) {
            userContext = new HashMap<>();
            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
        } else {
            userContext = null;
        }

        final AuthorizationRequest request = new AuthorizationRequest.Builder()
                .resource(ResourceFactory.getSiteToSiteResource()).identity(user.getIdentity())
                .anonymous(user.isAnonymous()).accessAttempt(true).action(RequestAction.READ)
                .userContext(userContext).explanationSupplier(() -> "Unable to retrieve site to site details.")
                .build();

        final AuthorizationResult result = authorizer.authorize(request);
        if (!Result.Approved.equals(result.getResult())) {
            throw new AccessDeniedException(result.getExplanation());
        }
    }

    /**
     * Returns the details of this NiFi.
     *
     * @return A controllerEntity.
     */
    @GET
    @Consumes(MediaType.WILDCARD)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Returns the details about this NiFi necessary to communicate via site to site", response = ControllerEntity.class, authorizations = {
            @Authorization(value = "Read - /site-to-site", type = "") })
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
            @ApiResponse(code = 401, message = "Client could not be authenticated."),
            @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
            @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") })
    public Response getSiteToSiteDetails(@Context HttpServletRequest req) {

        authorizeSiteToSite();

        if (isReplicateRequest()) {
            return replicate(HttpMethod.GET);
        }

        // get the controller dto
        final ControllerDTO controller = serviceFacade.getSiteToSiteDetails();

        // build the response entity
        final ControllerEntity entity = new ControllerEntity();
        entity.setController(controller);

        if (isEmpty(req.getHeader(HttpHeaders.PROTOCOL_VERSION))) {
            // This indicates the client uses older NiFi version,
            // which strictly read JSON properties and fail with unknown properties.
            // Convert result entity so that old version clients can understand.
            logger.debug("Converting result to provide backward compatibility...");
            controller.setRemoteSiteHttpListeningPort(null);
        }

        // generate the response
        return clusterContext(noCache(Response.ok(entity))).build();
    }

    /**
     * Returns the available Peers and its status of this NiFi.
     *
     * @return A peersEntity.
     */
    @GET
    @Path("/peers")
    @Consumes(MediaType.WILDCARD)
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @ApiOperation(value = "Returns the available Peers and its status of this NiFi", response = PeersEntity.class, authorizations = {
            @Authorization(value = "Read - /site-to-site", type = "") })
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
            @ApiResponse(code = 401, message = "Client could not be authenticated."),
            @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
            @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") })
    public Response getPeers(@Context HttpServletRequest req) {

        authorizeSiteToSite();

        if (!properties.isSiteToSiteHttpEnabled()) {
            return responseCreator.httpSiteToSiteIsNotEnabledResponse();
        }

        final Integer transportProtocolVersion;
        try {
            transportProtocolVersion = negotiateTransportProtocolVersion(req, transportProtocolVersionNegotiator);
        } catch (BadRequestException e) {
            return responseCreator.badRequestResponse(e);
        }

        final List<PeerDTO> peers = new ArrayList<>();
        if (properties.isNode()) {

            try {
                final Map<NodeIdentifier, NodeWorkload> clusterWorkload = clusterCoordinator.getClusterWorkload();
                clusterWorkload.entrySet().stream().forEach(entry -> {
                    final PeerDTO peer = new PeerDTO();
                    final NodeIdentifier nodeId = entry.getKey();
                    final String siteToSiteAddress = nodeId.getSiteToSiteAddress();
                    peer.setHostname(siteToSiteAddress == null ? nodeId.getApiAddress() : siteToSiteAddress);
                    peer.setPort(nodeId.getSiteToSiteHttpApiPort() == null ? nodeId.getApiPort()
                            : nodeId.getSiteToSiteHttpApiPort());
                    peer.setSecure(nodeId.isSiteToSiteSecure());
                    peer.setFlowFileCount(entry.getValue().getFlowFileCount());
                    peers.add(peer);
                });
            } catch (IOException e) {
                throw new RuntimeException("Failed to retrieve cluster workload due to " + e, e);
            }

        } else {
            // Standalone mode.
            final PeerDTO peer = new PeerDTO();

            // Private IP address or hostname may not be accessible from client in some environments.
            // So, use the value defined in nifi.properties instead when it is defined.
            final String remoteInputHost = properties.getRemoteInputHost();
            String localName;
            try {
                // Get local host name using InetAddress if available, same as RAW socket does.
                localName = InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Failed to get local host name using InetAddress.", e);
                }
                localName = req.getLocalName();
            }

            peer.setHostname(isEmpty(remoteInputHost) ? localName : remoteInputHost);
            peer.setPort(properties.getRemoteInputHttpPort());
            peer.setSecure(properties.isSiteToSiteSecure());
            peer.setFlowFileCount(0); // doesn't matter how many FlowFiles we have, because we're the only host.

            peers.add(peer);
        }

        final PeersEntity entity = new PeersEntity();
        entity.setPeers(peers);

        return clusterContext(
                noCache(setCommonHeaders(Response.ok(entity), transportProtocolVersion, transactionManager)))
                        .build();
    }

    // setters

    public void setServiceFacade(final NiFiServiceFacade serviceFacade) {
        this.serviceFacade = serviceFacade;
    }

    public void setAuthorizer(Authorizer authorizer) {
        this.authorizer = authorizer;
    }

    @Override
    public void setClusterCoordinator(final ClusterCoordinator clusterCoordinator) {
        super.setClusterCoordinator(clusterCoordinator);
        this.clusterCoordinator = clusterCoordinator;
    }
}