<commit_before>package org.aktin.broker;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.Variant;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;

import org.aktin.broker.auth.AuthCache;
import org.aktin.broker.auth.Principal;
import org.aktin.broker.db.BrokerBackend;
import org.aktin.broker.notify.BrokerWebsocket;
import org.aktin.broker.server.DateDataSource;
import org.aktin.broker.xml.BrokerStatus;
import org.aktin.broker.xml.Node;
import org.aktin.broker.xml.NodeList;
import org.aktin.broker.xml.RequestInfo;
import org.aktin.broker.xml.RequestList;
import org.aktin.broker.xml.RequestStatus;
import org.aktin.broker.xml.RequestStatusInfo;
import org.aktin.broker.xml.RequestStatusList;
import org.aktin.broker.xml.RequestTargetNodes;

/**
 * Broker service.
 * 
 * TODO allow JSON media types (via JAXB mapping e.g. with MOXY)
 * TODO add RequestTypeManager
 * @author R.W.Majeed
 *
 */
@Path(BrokerEndpoint.SERVICE_URL)
public class BrokerEndpoint {
	private static final Logger log = Logger.getLogger(BrokerEndpoint.class.getName());
	public static final String SERVICE_URL = "/broker/";

	@Inject
	private BrokerBackend db;

	@Inject
	private AuthCache auth; // for cached last contact timestamp

	@Inject
	RequestTypeManager typeManager;

	/**
	 * Retrieve status information about the broker.
	 * @return JSON status
	 */
	@GET
	@Path("status")
	@Produces(MediaType.APPLICATION_XML)
	public BrokerStatus status(){
		return BrokerStatus.create();
	}
	
	
	/**
	 * Retrieve a list of registered nodes with the
	 * broker.
	 * @return JSON list of nodes
	 */
	@GET
	@Path("node")
	@Produces(MediaType.APPLICATION_XML)
	public Response allNodes(){
		try {
			List<Node> nodes = db.getAllNodes();
			auth.fillCachedAccessTimestamps(nodes);
			return Response.ok(new NodeList(nodes)).build();
			// look up cached last contact in AuthCache
		} catch (SQLException e) {
			log.log(Level.SEVERE, "unable to retrieve node list", e);
			return Response.serverError().build();
		}
	}
	/**
	 * Retrieve information about a single node.
	 * @param nodeId node id
	 * @return status {@code 200} with node info or status {@code 404} if not found. 
	 */
	@GET
	@Path("node/{id}")
	@Produces(MediaType.APPLICATION_XML)
	public Node getNodeInfo(@PathParam("id") int nodeId){
		Node node;
		try {
			node = db.getNode(nodeId);
			if( node == null ){
				throw new NotFoundException();
			}
			// look up cached last contact in AuthCache
			auth.fillCachedAccessTimestamps(Collections.singletonList(node));
		} catch (SQLException e) {
			log.log(Level.SEVERE, "unable to retrieve node list", e);
			throw new InternalServerErrorException(e);
		}
		return node;
	}
	/**
	 * Retrieve a named resource uploaded previously by the specified node.
	 * <p>
	 * Last modified and eTag headers will be set.
	 * The eTag is calculated {@code url-safe-base64(sha-256(data))}.
	 * </p>
	 * @param nodeId node id
	 * @return status {@code 200} with node info or status {@code 404} if not found. 
	 * @throws SQLException sql error
	 */
	@GET
	@Path("node/{node}/{resource}")
	public Response getNodeResource(@PathParam("node") int nodeId, @PathParam("resource") String resourceId) throws SQLException{		
		DateDataSource ds = db.getNodeResource(nodeId, resourceId);
		if( ds == null ){
			throw new NotFoundException();
		}
		ResponseBuilder resp = Response.ok(ds, ds.getContentType());
		// set etag
		if( ds instanceof DigestPathDataSource ){
			resp.tag(Base64.getUrlEncoder().encodeToString(((DigestPathDataSource)ds).sha256));
			resp.header("Content-MD5", Base64.getUrlEncoder().encodeToString(((DigestPathDataSource)ds).md5));
		}
		// set last modified
		resp.lastModified(Date.from(ds.getLastModified()));
		return resp.build();
	}
	
	/**
	 * Upload node resources to the broker
	 * @param status JSON status
	 * @param sec security context
	 * @throws SQLException 
	 */
	@Authenticated
	@PUT
	@Path("my/node/{resource}")
	public void setNodesResource(@PathParam("resource") String resourceId, @Context HttpHeaders headers, @Context SecurityContext sec, InputStream content) {
		Principal user = (Principal)sec.getUserPrincipal();
		log.info("Resource uploaded by node "+user.getNodeId()+": "+resourceId);
		try {
			db.updateNodeResource(user.getNodeId(), resourceId, headers.getMediaType(), content);
		} catch (IOException | SQLException e) {
			log.log(Level.SEVERE, "Unable to write resource for node "+user.getNodeId()+": "+resourceId, e);
			throw new InternalServerErrorException(e);
		}
	}
	
	@Authenticated
	@GET
	@Path("my/node")
	@Produces(MediaType.APPLICATION_XML)
	// TODO add unit test
	public Node getOwnNodeInfo(@Context SecurityContext sec){
		Principal user = (Principal)sec.getUserPrincipal();
		try {
			return db.getNode(user.getNodeId());
		} catch (SQLException e) {
			log.log(Level.SEVERE, "Unable to read node info for nodeId="+user.getNodeId(), e);
			throw new InternalServerErrorException(e);
		}
	}<commit_msg>javadoc fixed<commit_after>package org.aktin.broker;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.Variant;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;

import org.aktin.broker.auth.AuthCache;
import org.aktin.broker.auth.Principal;
import org.aktin.broker.db.BrokerBackend;
import org.aktin.broker.notify.BrokerWebsocket;
import org.aktin.broker.server.DateDataSource;
import org.aktin.broker.xml.BrokerStatus;
import org.aktin.broker.xml.Node;
import org.aktin.broker.xml.NodeList;
import org.aktin.broker.xml.RequestInfo;
import org.aktin.broker.xml.RequestList;
import org.aktin.broker.xml.RequestStatus;
import org.aktin.broker.xml.RequestStatusInfo;
import org.aktin.broker.xml.RequestStatusList;
import org.aktin.broker.xml.RequestTargetNodes;

/**
 * Broker service.
 * 
 * TODO allow JSON media types (via JAXB mapping e.g. with MOXY)
 * TODO add RequestTypeManager
 * @author R.W.Majeed
 *
 */
@Path(BrokerEndpoint.SERVICE_URL)
public class BrokerEndpoint {
	private static final Logger log = Logger.getLogger(BrokerEndpoint.class.getName());
	public static final String SERVICE_URL = "/broker/";

	@Inject
	private BrokerBackend db;

	@Inject
	private AuthCache auth; // for cached last contact timestamp

	@Inject
	RequestTypeManager typeManager;

	/**
	 * Retrieve status information about the broker.
	 * @return JSON status
	 */
	@GET
	@Path("status")
	@Produces(MediaType.APPLICATION_XML)
	public BrokerStatus status(){
		return BrokerStatus.create();
	}
	
	
	/**
	 * Retrieve a list of registered nodes with the
	 * broker.
	 * @return JSON list of nodes
	 */
	@GET
	@Path("node")
	@Produces(MediaType.APPLICATION_XML)
	public Response allNodes(){
		try {
			List<Node> nodes = db.getAllNodes();
			auth.fillCachedAccessTimestamps(nodes);
			return Response.ok(new NodeList(nodes)).build();
			// look up cached last contact in AuthCache
		} catch (SQLException e) {
			log.log(Level.SEVERE, "unable to retrieve node list", e);
			return Response.serverError().build();
		}
	}
	/**
	 * Retrieve information about a single node.
	 * @param nodeId node id
	 * @return status {@code 200} with node info or status {@code 404} if not found. 
	 */
	@GET
	@Path("node/{id}")
	@Produces(MediaType.APPLICATION_XML)
	public Node getNodeInfo(@PathParam("id") int nodeId){
		Node node;
		try {
			node = db.getNode(nodeId);
			if( node == null ){
				throw new NotFoundException();
			}
			// look up cached last contact in AuthCache
			auth.fillCachedAccessTimestamps(Collections.singletonList(node));
		} catch (SQLException e) {
			log.log(Level.SEVERE, "unable to retrieve node list", e);
			throw new InternalServerErrorException(e);
		}
		return node;
	}
	/**
	 * Retrieve a named resource uploaded previously by the specified node.
	 * <p>
	 * Last modified and eTag headers will be set.
	 * The eTag is calculated {@code url-safe-base64(sha-256(data))}.
	 * </p>
	 * @param nodeId node id
	 * @param resourceId resource id
	 * @return status {@code 200} with node info or status {@code 404} if not found. 
	 * @throws SQLException sql error
	 */
	@GET
	@Path("node/{node}/{resource}")
	public Response getNodeResource(@PathParam("node") int nodeId, @PathParam("resource") String resourceId) throws SQLException{		
		DateDataSource ds = db.getNodeResource(nodeId, resourceId);
		if( ds == null ){
			throw new NotFoundException();
		}
		ResponseBuilder resp = Response.ok(ds, ds.getContentType());
		// set etag
		if( ds instanceof DigestPathDataSource ){
			resp.tag(Base64.getUrlEncoder().encodeToString(((DigestPathDataSource)ds).sha256));
			resp.header("Content-MD5", Base64.getUrlEncoder().encodeToString(((DigestPathDataSource)ds).md5));
		}
		// set last modified
		resp.lastModified(Date.from(ds.getLastModified()));
		return resp.build();
	}
	
	/**
	 * Upload node resources to the broker
	 * @param resourceId resource id
	 * @param headers HTTP headers. Used to retrieve media type
	 * @param sec security context
	 * @param content resource content to store
	 */
	@Authenticated
	@PUT
	@Path("my/node/{resource}")
	public void setNodesResource(@PathParam("resource") String resourceId, @Context HttpHeaders headers, @Context SecurityContext sec, InputStream content) {
		Principal user = (Principal)sec.getUserPrincipal();
		log.info("Resource uploaded by node "+user.getNodeId()+": "+resourceId);
		try {
			db.updateNodeResource(user.getNodeId(), resourceId, headers.getMediaType(), content);
		} catch (IOException | SQLException e) {
			log.log(Level.SEVERE, "Unable to write resource for node "+user.getNodeId()+": "+resourceId, e);
			throw new InternalServerErrorException(e);
		}
	}
	
	@Authenticated
	@GET
	@Path("my/node")
	@Produces(MediaType.APPLICATION_XML)
	// TODO add unit test
	public Node getOwnNodeInfo(@Context SecurityContext sec){
		Principal user = (Principal)sec.getUserPrincipal();
		try {
			return db.getNode(user.getNodeId());
		} catch (SQLException e) {
			log.log(Level.SEVERE, "Unable to read node info for nodeId="+user.getNodeId(), e);
			throw new InternalServerErrorException(e);
		}
	}
{'fraction_non_alphanumeric': 0.12478934951129086, 'fraction_numerical': 0.0047185709470845974, 'mean_word_length': 4.5540477304632665, 'pattern_counts': {'":': 4, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 18}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5216769', 'n_tokens_mistral': 4036, 'n_tokens_neox': 3788, 'n_words': 946}