edu.cmu.cs.lti.discoursedb.api.browsing.controller.BrowsingRestController.java Source code

Java tutorial

Introduction

Here is the source code for edu.cmu.cs.lti.discoursedb.api.browsing.controller.BrowsingRestController.java

Source

/*******************************************************************************
 * Copyright (C)  2015 - 2016  Carnegie Mellon University
 * Authors: Oliver Ferschke and Chris Bogart
 *
 * This file is part of DiscourseDB.
 *
 * DiscourseDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * DiscourseDB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DiscourseDB.  If not, see <http://www.gnu.org/licenses/> 
 * or write to the Free Software Foundation, Inc., 51 Franklin Street, 
 * Fifth Floor, Boston, MA 02110-1301  USA
 *******************************************************************************/
package edu.cmu.cs.lti.discoursedb.api.browsing.controller;

import java.beans.PropertyDescriptor;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.persistence.Table;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.expression.BeanExpressionContextAccessor;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.GsonFactoryBean;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import edu.cmu.cs.lti.discoursedb.annotation.brat.io.BratService;
import edu.cmu.cs.lti.discoursedb.annotation.lightside.io.LightSideService;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingBratExportResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingContributionResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingDatabasesResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingDiscoursePartResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingDiscourseResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingLightsideStubsResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingStatsResource;
import edu.cmu.cs.lti.discoursedb.api.browsing.resource.BrowsingUserResource;
import edu.cmu.cs.lti.discoursedb.api.query.DdbQuery;
import edu.cmu.cs.lti.discoursedb.api.query.DdbQuery.DdbQueryParseException;
import edu.cmu.cs.lti.discoursedb.configuration.DatabaseSelector;
import edu.cmu.cs.lti.discoursedb.core.model.macro.Contribution;
import edu.cmu.cs.lti.discoursedb.core.model.macro.Discourse;
import edu.cmu.cs.lti.discoursedb.core.model.macro.DiscoursePart;
import edu.cmu.cs.lti.discoursedb.core.model.macro.DiscourseToDiscoursePart;
import edu.cmu.cs.lti.discoursedb.core.model.user.User;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.ContributionRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.DiscoursePartContributionRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.DiscoursePartRelationRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.DiscoursePartRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.DiscourseRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.macro.DiscourseToDiscoursePartRepository;
import edu.cmu.cs.lti.discoursedb.system.repository.system.SystemUserRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.user.DiscoursePartInteractionRepository;
import edu.cmu.cs.lti.discoursedb.core.repository.user.UserRepository;
import edu.cmu.cs.lti.discoursedb.core.service.annotation.AnnotationService;
import edu.cmu.cs.lti.discoursedb.core.service.macro.DiscoursePartService;
import edu.cmu.cs.lti.discoursedb.system.service.system.SystemUserService;
import edu.cmu.cs.lti.discoursedb.core.service.user.UserService;
import edu.cmu.cs.lti.discoursedb.system.model.system.SystemUserProperty;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;

@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "/browsing", produces = "application/hal+json")
public class BrowsingRestController {

    private static final Logger logger = LogManager.getLogger(BrowsingRestController.class);

    @Autowired
    private BratService bratService;

    @Autowired
    private LightSideService lightsideService;

    @Autowired
    private Environment environment;

    @Autowired
    private DiscourseRepository discourseRepository;

    @Autowired
    private DiscoursePartRepository discoursePartRepository;

    @Autowired
    private DiscourseToDiscoursePartRepository discourseToDiscoursePartRepository;

    @Autowired
    private SystemUserService systemUserService;

    @Autowired
    private UserService userService;

    @Autowired
    private DiscoursePartRelationRepository discoursePartRelationRepository;

    @Autowired
    private DiscoursePartInteractionRepository discoursePartInteractionRepository;

    @Autowired
    DiscoursePartContributionRepository discoursePartContributionRepository;

    @Autowired
    private ContributionRepository contributionRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private HttpSession httpSession;

    @Autowired
    PagedResourcesAssembler<BrowsingDiscoursePartResource> praDiscoursePartAssembler;
    @Autowired
    PagedResourcesAssembler<BrowsingDiscourseResource> praDiscourseAssembler;
    @Autowired
    PagedResourcesAssembler<BrowsingContributionResource> praContributionAssembler;
    @Autowired
    PagedResourcesAssembler<BrowsingBratExportResource> praBratAssembler;
    @Autowired
    PagedResourcesAssembler<BrowsingLightsideStubsResource> praLSAssembler;
    @Autowired
    PagedResourcesAssembler<BrowsingUserResource> praUserAssembler;
    @Autowired
    SecurityUtils securityUtils;
    @Autowired
    private DiscoursePartService discoursePartService;
    //@Autowired private SystemUserRepository sysUserRepo;
    @Autowired
    private SystemUserService sysUserSvc;
    @Autowired
    private ApplicationContext appContext;
    @Autowired
    private AnnotationService annoService;
    @Autowired
    DatabaseSelector selector;

    void registerDb(HttpServletRequest httpServletRequest, HttpSession session, String databaseName) {
        securityUtils.authenticate(httpServletRequest, session);
        securityUtils.authorizedDatabaseCheck(databaseName);
        selector.changeDatabase(databaseName);
    }

    @RequestMapping(value = "/database/{databaseName}/stats", method = RequestMethod.GET)
    @ResponseBody
    Resources<BrowsingStatsResource> stats(@PathVariable("databaseName") String databaseName,
            HttpServletRequest httpServletRequest, HttpSession session) {
        registerDb(httpServletRequest, session, databaseName);

        BrowsingStatsResource bsr = new BrowsingStatsResource(discourseRepository, discoursePartRepository,
                contributionRepository, userRepository, securityUtils);

        List<BrowsingStatsResource> l = new ArrayList<BrowsingStatsResource>();
        l.add(bsr);
        Resources<BrowsingStatsResource> r = new Resources<BrowsingStatsResource>(l);
        /*for (String t: bsr.getDiscourseParts().keySet()) {
           r.add(makeLink("/browsing/repos?repoType=" + t, t));         
        }
        r.add(makeLink("/browsing/bratExports", "BRAT markup"));
        r.add(makeLink("/browsing/lightsideExports", "Lightside exports"));
        */

        return r;
    }

    @RequestMapping(value = "/databases", method = RequestMethod.GET)
    @ResponseBody
    Resources<BrowsingDatabasesResource> databases(HttpServletRequest httpServletRequest, HttpSession session) {
        securityUtils.authenticate(httpServletRequest, session);

        BrowsingDatabasesResource bsr = new BrowsingDatabasesResource(securityUtils.allowedDatabases());

        List<BrowsingDatabasesResource> l = new ArrayList<BrowsingDatabasesResource>();
        l.add(bsr);
        Resources<BrowsingDatabasesResource> r = new Resources<BrowsingDatabasesResource>(l);

        return r;
    }

    @RequestMapping(value = "/roles", method = RequestMethod.GET)
    @ResponseBody
    List<String> roles(HttpServletRequest httpServletRequest, HttpSession session) {
        securityUtils.authenticate(httpServletRequest, session);
        return securityUtils.allowedRoles();
    }

    @RequestMapping(value = "/refresh", method = RequestMethod.GET)
    @ResponseBody
    void refresh(HttpServletRequest httpServletRequest, HttpSession session) {
        securityUtils.authenticate(httpServletRequest, session);
        systemUserService.refreshOpenDatabases();

        return;
    }

    @ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "User does not have access to this database")
    static public class UnauthorizedDatabaseAccess extends RuntimeException {

    }

    @RequestMapping(value = "/database/{databaseName}/discourses/{discourseId}", method = RequestMethod.GET)
    @CrossOrigin(origins = "*", maxAge = 3600)
    @ResponseBody
    @Secured("ADMIN")
    Resource<BrowsingDiscourseResource> discourses(@PathVariable("databaseName") String databaseName,
            @PathVariable("discourseId") Long discourseId, HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);
        BrowsingDiscourseResource bsr = new BrowsingDiscourseResource(
                discourseRepository.findOne(discourseId).get(), discoursePartRepository);

        Resource<BrowsingDiscourseResource> r = new Resource<BrowsingDiscourseResource>(bsr);
        logger.info(
                "currently logged in is " + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        logger.info("another check " + httpSession.getAttribute("sch"));
        return r;

    }

    @RequestMapping(value = {
            "/database/{databaseName}/discourses/{discourseId}/discoursePartTypes/{discoursePartType}",
            "/discourses/{discourseId}/discoursePartTypes" }, method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingDiscoursePartResource>> discoursePartsByTypeAndDiscourse(
            @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "20") int size,
            @PathVariable("databaseName") String databaseName, @PathVariable("discourseId") Long discourseId,
            @PathVariable("discoursePartType") Optional<String> discoursePartType,
            @RequestParam(value = "annoType", defaultValue = "*") String annoType, HttpServletRequest hsr,
            HttpSession session) {
        registerDb(hsr, session, databaseName);
        PageRequest p = new PageRequest(page, size);

        Page<BrowsingDiscoursePartResource> repoResources = null;

        if (!discoursePartType.isPresent()) {
            repoResources = discoursePartRepository.findAllByDiscourse(discourseId, p)
                    .map(dp -> new BrowsingDiscoursePartResource(dp, annoService)).map(bdpr -> {
                        bdpr.filterAnnotations(annoType);
                        bdpr.fillInUserInteractions(discoursePartInteractionRepository, annoService);
                        return bdpr;
                    });
        } else {
            repoResources = discoursePartRepository
                    .findAllByDiscourseAndType(discoursePartType.get(), discourseId, p)
                    .map(dp -> new BrowsingDiscoursePartResource(dp, annoService)).map(bdpr -> {
                        bdpr.filterAnnotations(annoType);
                        bdpr.fillInUserInteractions(discoursePartInteractionRepository, annoService);
                        return bdpr;
                    });
        }

        repoResources.forEach(bcr -> {
            if (bcr.getContainingDiscourseParts().size() > 1) {
                bcr._getContainingDiscourseParts().forEach((dpId, dpname) -> {
                    bcr.add(makeLink("/browsing/database/" + databaseName + "/usersInDiscourseParts/" + dpId,
                            "users in " + dpname));
                    bcr.add(makeLink("/browsing/database/" + databaseName + "/subDiscourseParts/" + dpId, dpname));
                });
            }
            //Link check = makeLink1Arg("/browsing/action/exportLightside","chk:Export to Lightside: no annotations", "parentDpId", Long.toString(bcr._getDpId()));
            //Link check2 = makeLink2Arg("/browsing/action/exportLightside","chk:Export to Lightside: with annotations", "withAnnotations", "true", "parentDpId", Long.toString(bcr._getDpId()));
            Link check = makeLightsideExportNameLink(
                    "/browsing/action/database/" + databaseName + "/exportLightside", false,
                    "chk:Export to Lightside, no annotations", bcr.getName(), Long.toString(bcr._getDpId()));
            Link check2 = makeLightsideExportNameLink(
                    "/browsing/action/database/" + databaseName + "/exportLightside", true,
                    "chk:Export to Lightside, with annotations", bcr.getName(), Long.toString(bcr._getDpId()));
            bcr.add(check);
            bcr.add(check2);
            Link check3 = makeBratExportNameLink("/browsing/action/database/" + databaseName + "/exportBratItem",
                    "chk:Export to BRAT", bcr.getName(), Long.toString(bcr._getDpId()));
            bcr.add(check3);
        });

        PagedResources<Resource<BrowsingDiscoursePartResource>> response = praDiscoursePartAssembler
                .toResource(repoResources);

        return response;
    }

    //@Secured("ROLE_ADMIN")
    @RequestMapping(value = "/database/{databaseName}/discourses", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingDiscourseResource>> discourse(@PathVariable("databaseName") String databaseName,
            @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "20") int size, HttpServletRequest hsr,
            HttpSession session) {
        registerDb(hsr, session, databaseName);
        PageRequest p = new PageRequest(page, size);

        Page<BrowsingDiscourseResource> discourseResources = discourseRepository.findAll(p)
                .map(b -> new BrowsingDiscourseResource(b, discoursePartRepository));

        /*         discourseResources.forEach(
         bcr -> {
            Long dpId = bcr.getDiscourseId();
            Link check0 = makeLink2Arg("/browsing/action/exportBratItem","chk:Export to BRAT", "parentDpId", dpId.toString(), "childDpId", Long.toString(bcr._getDpId()));
            bcr.add(check0);
            Link check = makeLink1Arg("/browsing/action/exportLightside","chk:Export to Lightside: no annotations", "parentDpId", Long.toString(bcr._getDpId()));
            Link check2 = makeLink2Arg("/browsing/action/exportLightside","chk:Export to Lightside: with annotations", "withAnnotations", "true", "parentDpId", Long.toString(bcr._getDpId()));
            bcr.add(check);   
            bcr.add(check2);
             
         }
                 );*/
        PagedResources<Resource<BrowsingDiscourseResource>> response = praDiscourseAssembler
                .toResource(discourseResources);

        /*
         * Disabling exportBrat link overall -- instead add for each thread item as a checkbox
         *
        long threadcount = 0L;
        for (BrowsingDiscoursePartResource bdpr: repoResources) {
        threadcount += bdpr.getContributionCount();
        }
            
        if (threadcount > 0) {
        response.add(makeLink1Arg("/browsing/action/exportBrat", "Export these all to BRAT", "parentDpId", dpId.toString()));
        }
         */
        //      response.add(makeLink("/browsing/usersInDiscoursePart/" + dpId, "show users"));

        return response;

    }

    /*
    @RequestMapping(value = "/database/{databaseName}/repos", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingDiscoursePartResource>> discourseParts(@RequestParam(value= "page", defaultValue = "0") int page, 
                                         @RequestParam(value= "size", defaultValue="20") int size,
                                         @PathVariable("databaseName") String databaseName,
                                         @RequestParam("repoType") String repoType,
                                         @RequestParam(value="annoType", defaultValue="*") String annoType,
                                         HttpServletRequest hsr, HttpSession session) {
       registerDb(hsr, session, databaseName);
       PageRequest p = new PageRequest(page,size);
       Page<BrowsingDiscoursePartResource> repoResources = 
        discoursePartRepository.findAllNonDegenerateByType(repoType, p)
        .map(BrowsingDiscoursePartResource::new)
        .map(bdpr -> {bdpr.filterAnnotations(annoType); 
                      bdpr.fillInUserInteractions(discoursePartInteractionRepository);
                      return bdpr; });
            
       repoResources.forEach(bcr -> {
     if (bcr.getContainingDiscourseParts().size() > 1) { 
        bcr._getContainingDiscourseParts().forEach(
          (dpId, dpname) -> {
             bcr.add(makeLink("/browsing/usersInDiscourseParts/" + dpId, "users in " + dpname));
             bcr.add(makeLink("/browsing/subDiscourseParts/" + dpId, dpname));
          });
     }  
     //Link check = makeLink1Arg("/browsing/action/exportLightside","chk:Export to Lightside: no annotations", "parentDpId", Long.toString(bcr._getDpId()));
      //Link check2 = makeLink2Arg("/browsing/action/exportLightside","chk:Export to Lightside: with annotations", "withAnnotations", "true", "parentDpId", Long.toString(bcr._getDpId()));
     Link check = makeLightsideExportNameLink("/browsing/action/exportLightside",false,"chk:Export to Lightside, no annotations", bcr.getName(), Long.toString(bcr._getDpId()));
      Link check2 = makeLightsideExportNameLink("/browsing/action/exportLightside",true,"chk:Export to Lightside, with annotations", bcr.getName(), Long.toString(bcr._getDpId()));
      bcr.add(check);   
      bcr.add(check2);
      Link check3 = makeBratExportNameLink("/browsing/action/exportBratItem","chk:Export to BRAT", repoType,  Long.toString(bcr._getDpId()));
      bcr.add(check3);
       });
           
       PagedResources<Resource<BrowsingDiscoursePartResource>> response = praDiscoursePartAssembler.toResource(repoResources);
        
       return response;
    }
    */

    public String sanitize(String name) {
        return name.replaceAll("[^a-zA-Z0-9]", "_");
    }

    public String sanitize_dirname(String name) {
        return name.replaceAll("[^a-zA-Z0-9\\._]", "_");
    }

    public Optional<DiscoursePart> bratName2DiscoursePart(String filename) {
        try {
            String[] parts = filename.split("__");
            if (parts.length > 1) {
                long dpid = Long.parseLong(parts[parts.length - 1]);
                logger.info(" final part of " + filename + " is " + dpid);
                return discoursePartRepository.findOne(dpid);
            }
        } catch (Exception e) {
            logger.info("No success identifying brat import directory " + filename + e.toString());
        }
        return Optional.empty();
    }

    public Optional<DiscoursePart> lightsideName2DiscoursePartForImport(String filename) {
        if (filename.endsWith(".csv") && filename.contains("_annotated_")) {
            return bratName2DiscoursePart(filename.substring(0, filename.length() - 4));
        } else {
            return Optional.empty();
        }
    }

    public String discoursePart2LightSideName(DiscoursePart dp, boolean withAnnotations) {
        return dp.getName().replaceAll("[^a-zA-Z0-9]", "_") + (withAnnotations ? "_annotated_" : "") + "__"
                + dp.getId() + ".csv".toString();
    }

    public String exportFile2LightSideTempName(String exportFile, boolean withAnnotations) {
        return exportFile.replaceAll("[^a-zA-Z0-9]", "_") + (withAnnotations ? "_annotated" : "")
                + ".csv".toString();
    }

    public String exportFile2LightSideDir(String exportFile, boolean withAnnotations) {
        return exportFile.replaceAll("[^a-zA-Z0-9]", "_") + (withAnnotations ? "_annotated" : "").toString();
    }

    @RequestMapping(value = "/action/database/{databaseName}/deleteLightside", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingLightsideStubsResource>> deleteLightside(
            @PathVariable("databaseName") String databaseName,
            @RequestParam(value = "exportFilename") String exportFilename,
            @RequestParam(value = "withAnnotations", defaultValue = "false") boolean withAnnotations,
            HttpServletRequest hsr, HttpSession session) throws IOException {
        registerDb(hsr, session, databaseName);
        String lsDataDirectory = lsDataDirectory();
        File lsOutputFilename = new File(lsDataDirectory, exportFile2LightSideDir(exportFilename, withAnnotations));
        logger.info("DeleteLightside: dd:" + lsDataDirectory + " ef:" + exportFilename + " wa:" + withAnnotations
                + " => " + lsOutputFilename.toString());
        for (File f : lsOutputFilename.listFiles()) {
            f.delete();
        }
        lsOutputFilename.delete();
        return lightsideExports(databaseName, hsr, session);
    }

    @Deprecated
    @CrossOrigin(origins = "*", maxAge = 3600)
    @RequestMapping(value = "/tokensigningoogle_deprecated", method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
    public String processRegistration(@RequestParam("idtoken") String idTokenString) //, ModelMap model)
            throws GeneralSecurityException, IOException {
        logger.info("Doing tokensigningoogle");
        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(),
                new GsonFactory()).setAudience(Arrays.asList(environment.getRequiredProperty("google.client_id")))
                        .setIssuer("accounts.google.com").build();

        GoogleIdToken idToken = verifier.verify(idTokenString);
        if (idToken != null) {
            Payload payload = idToken.getPayload();
            // Print user identifier
            String userId = payload.getSubject();
            // Get profile information from payload
            String email = payload.getEmail();
            logger.info("Logged in " + userId + " " + email);
            boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
            //List<User> users = DbFunction.listHqlNew("FROM User WHERE email = :email", "email", email);

            if (!emailVerified) { //|| users.isEmpty()) {
                return "/error.html";
            } else {
                //List<String> roles = DbFunction.listSQLNew(
                //        "SELECT role.name FROM user_role_association JOIN role ON role.id = role_id JOIN user on user.id = user_id WHERE user.email = :email",
                //        "email", email);

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                //for (String role : roles) {
                //    authorities.add(new SimpleGrantedAuthority(role));
                //}
                authorities.add(new SimpleGrantedAuthority("USER_AUTH0RITY"));

                UserDetails userDetails = new org.springframework.security.core.userdetails.User(userId, "xxy",
                        true, true, true, true, authorities);
                Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null,
                        userDetails.getAuthorities());
                //UserDetails userDetails = new org.springframework.security.core.userdetails.User(users.get(0).getName(),
                //        "xx", users.get(0).isEnabled(), true, true, true, authorities);
                //Authentication authentication = new UsernamePasswordAuthenticationToken(users.get(0).getName(), null,
                //        userDetails.getAuthorities());
                SecurityContextHolder.clearContext();
                SecurityContextHolder.getContext().setAuthentication(authentication);
                httpSession.setAttribute("sch", userDetails);
                logger.info("first check " + httpSession.getAttribute("sch"));

                return "/browsing/databases";
            }
        } else {
            System.out.println("Invalid ID token.");
        }
        return "/error.html";
    }

    @RequestMapping(value = "/action/database/{databaseName}/uploadLightside", headers = "content-type=multipart/*", method = RequestMethod.POST)
    @ResponseBody
    PagedResources<Resource<BrowsingLightsideStubsResource>> uploadLightside(
            @RequestParam("file_annotatedFileForUpload") MultipartFile file_annotatedFileForUpload,
            @PathVariable("databaseName") String databaseName, HttpServletRequest hsr, HttpSession session)
            throws IOException {
        registerDb(hsr, session, databaseName);
        String lsDataDirectory = lsDataDirectory();
        logger.info("Someone uploaded something!");
        if (!file_annotatedFileForUpload.isEmpty()) {
            try {
                logger.info("Not even empty!");
                File tempUpload = File.createTempFile("temp-file-name", ".csv");
                BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(tempUpload));
                FileCopyUtils.copy(file_annotatedFileForUpload.getInputStream(), stream);
                stream.close();
                lightsideService.importAnnotatedData(tempUpload.toString());
            } catch (Exception e) {
                logger.error("Error importing to lightside: " + e);
            }
        }
        return lightsideExports(databaseName, hsr, session);
    }

    @RequestMapping(value = "/action/downloadQueryCsv/discoursedb_data.csv", method = RequestMethod.GET)
    @ResponseBody
    String downloadQueryCsv(HttpServletResponse response, @RequestParam("query") String query,
            HttpServletRequest hsr, HttpSession session) throws IOException {

        securityUtils.authenticate(hsr, session);
        response.setContentType("application/csv; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment");

        try {
            logger.info("Got query for csv: " + query);

            DdbQuery q = new DdbQuery(selector, discoursePartService, query);

            Page<BrowsingContributionResource> lbcr = q.retrieveAllContributions()
                    .map(c -> new BrowsingContributionResource(c, annoService));

            StringBuilder output = new StringBuilder();
            ArrayList<String> headers = new ArrayList<String>();
            for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(BrowsingContributionResource.class)) {
                String name = pd.getName();
                if (!name.equals("class") && !name.equals("id") && !name.equals("links")) {
                    headers.add(name);
                }
            }
            output.append(String.join(",", headers));
            output.append("\n");

            for (BrowsingContributionResource bcr : lbcr.getContent()) {
                String comma = "";
                BeanWrapper wrap = PropertyAccessorFactory.forBeanPropertyAccess(bcr);
                for (String hdr : headers) {

                    String item = "";
                    try {
                        item = wrap.getPropertyValue(hdr).toString();
                        item = item.replaceAll("\"", "\"\"");
                        item = item.replaceAll("^\\[(.*)\\]$", "$1");
                    } catch (Exception e) {
                        logger.info(e.toString() + " For header " + hdr + " item " + item);
                        item = "";
                    }
                    if (hdr.equals("annotations") && item.length() > 0) {
                        logger.info("Annotation is " + item);
                    }
                    output.append(comma + "\"" + item + "\"");
                    comma = ",";
                }
                output.append("\n");
            }
            return output.toString();
        } catch (Exception e) {
            return "ERROR:" + e.getMessage();
        }
    }

    @RequestMapping(value = "/action/downloadLightsideQuery/{exportFilename}.csv", method = RequestMethod.GET)
    @ResponseBody
    String downloadLightsideQuery(HttpServletResponse response, @RequestParam(value = "query") String query,
            @PathVariable(value = "exportFilename") String exportFilename,
            @RequestParam(value = "withAnnotations", defaultValue = "false") String withAnnotations,
            HttpServletRequest hsr, HttpSession session) throws IOException, DdbQueryParseException {

        DdbQuery q = new DdbQuery(selector, discoursePartService, query);
        registerDb(hsr, session, q.getDatabaseName());

        response.setContentType("application/csv; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment");
        String lsDataDirectory = lsDataDirectory();
        File lsOutputFileName = new File(lsDataDirectory, sanitize(exportFilename) + ".csv");
        if (withAnnotations.equals("true")) {
            lightsideService.exportAnnotations(q.getDiscourseParts(), lsOutputFileName);
        } else {
            lightsideService.exportDataForAnnotation(lsOutputFileName.toString(), q.retrieveAllContributions());
        }
        return FileUtils.readFileToString(lsOutputFileName);
    }

    @Deprecated
    @RequestMapping(value = "/action/database/{databaseName}/downloadLightside/{exportFilename}.csv", method = RequestMethod.GET)
    @ResponseBody
    String downloadLightside(HttpServletResponse response, @PathVariable("databaseName") String databaseName,
            @PathVariable(value = "exportFilename") String exportFilename,
            @RequestParam(value = "withAnnotations", defaultValue = "false") String withAnnotations,
            HttpServletRequest hsr, HttpSession session) throws IOException {

        registerDb(hsr, session, databaseName);

        response.setContentType("application/csv; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment");
        String lsDataDirectory = lsDataDirectory();
        File lsOutputFileDir = new File(lsDataDirectory, sanitize(exportFilename));
        File lsOutputFileName = new File(lsDataDirectory, sanitize(exportFilename) + ".csv");
        logger.info("Looking in directory " + lsOutputFileDir + " derived from " + exportFilename);
        Set<DiscoursePart> dps = Arrays.stream(lsOutputFileDir.listFiles())
                .map((File f) -> discoursePartRepository.findOne(Long.parseLong(f.getName())))
                .filter((Optional<DiscoursePart> o) -> o.isPresent()).map(o -> o.get()).collect(Collectors.toSet());

        if (withAnnotations.equals("true")) {
            logger.info("With annotations ", lsOutputFileName);
            lightsideService.exportAnnotations(dps, lsOutputFileName);
        } else {
            // For multiple discourseParts, need to assemble all the contributions
            logger.info("Without annotations ", lsOutputFileName);

            lightsideService.exportDataForAnnotation(lsOutputFileName.toString(),
                    dps.stream().flatMap(targ -> targ.getDiscoursePartContributions().stream())
                            .map(dpc -> dpc.getContribution())::iterator);
        }
        return FileUtils.readFileToString(lsOutputFileName);
    }

    //
    // Given a query string and a query name
    // cycle through all the discourse parts in the query and create a stub file for it
    @RequestMapping(value = "/action/database/{databaseName}/exportLightsideQuery", method = RequestMethod.GET)
    @ResponseBody
    @Deprecated
    String exportLightsideAction(@RequestParam("queryname") String queryname, @RequestParam("query") String query,
            @RequestParam(value = "withAnnotations", defaultValue = "false") boolean withAnnotations,
            HttpServletRequest hsr, HttpSession session) throws IOException, DdbQueryParseException {
        DdbQuery q = new DdbQuery(selector, discoursePartService, query);
        for (DiscoursePart dp : q.getDiscourseParts()) {
            exportLightsideAction(q.getDatabaseName(), queryname, withAnnotations, dp.getId(), hsr, session);
        }
        return "OK";
    }

    @RequestMapping(value = "/action/database/{databaseName}/exportLightside", method = RequestMethod.GET)
    @ResponseBody
    @Deprecated
    PagedResources<Resource<BrowsingLightsideStubsResource>> exportLightsideAction(
            @PathVariable("databaseName") String databaseName,
            @RequestParam(value = "exportFilename") String exportFilename,
            @RequestParam(value = "withAnnotations", defaultValue = "false") boolean withAnnotations,
            @RequestParam(value = "dpId") long dpId, HttpServletRequest hsr, HttpSession session)
            throws IOException {
        Assert.hasText(exportFilename, "No exportFilename specified");
        registerDb(hsr, session, databaseName);

        DiscoursePart dp = discoursePartRepository.findOne(dpId).get();
        String lsDataDirectory = lsDataDirectory();
        File lsOutputFilename = new File(lsDataDirectory, exportFile2LightSideDir(exportFilename, withAnnotations));
        logger.info(" Exporting dp " + dp.getName());
        Set<DiscoursePart> descendents = discoursePartService.findDescendentClosure(dp, Optional.empty());
        System.out.println(lsOutputFilename.getAbsoluteFile().toString());
        lsOutputFilename.mkdirs();
        for (DiscoursePart d : descendents) {
            File child = new File(lsOutputFilename, d.getId().toString());
            child.createNewFile();
        }
        return lightsideExports(databaseName, hsr, session);
    }

    //
    // We never need to do this.  When you upload a lightside-annotated file, the upload
    // function just imports it and forgets it.
    //
    @RequestMapping(value = "/action/database/{databaseName}/importLightside", method = RequestMethod.GET)
    @ResponseBody
    @Deprecated
    PagedResources<Resource<BrowsingLightsideStubsResource>> importLightsideAction(
            @RequestParam(value = "lightsideDirectory") String lightsideDirectory,
            @PathVariable("databaseName") String databaseName, HttpServletRequest hsr, HttpSession session)
            throws IOException {
        registerDb(hsr, session, databaseName);

        String liteDataDirectory = lsDataDirectory();
        lightsideService.importAnnotatedData(liteDataDirectory + "/" + lightsideDirectory);
        Optional<DiscoursePart> dp = lightsideName2DiscoursePartForImport(lightsideDirectory);
        //if (dp.isPresent()) {
        //   return subDiscourseParts(0,20, dp.get().getId());
        //} else {
        return lightsideExports(databaseName, hsr, session);
        //}
    }

    @RequestMapping(value = "/action/database/{databaseName}/deleteBrat", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingBratExportResource>> deleteBrat(
            @RequestParam(value = "bratDirectory") String bratDirectory,
            @PathVariable("databaseName") String databaseName, HttpServletRequest hsr, HttpSession session)
            throws IOException {
        registerDb(hsr, session, databaseName);
        String bratDataDirectory = bratDataDirectory();
        File bratDir = new File(bratDataDirectory + "/" + sanitize_dirname(bratDirectory));
        if (bratDir.isDirectory()) {
            FileUtils.deleteDirectory(bratDir);
        } else if (bratDir.isFile()) {
            bratDir.delete();
        }
        return bratExports(databaseName, hsr, session);
    }

    public String bratDataDirectory() {
        return environment.getRequiredProperty("brat.data_directory") + "/"
                + sysUserSvc.getSystemUser().get().getEmail();
    }

    public String lsDataDirectory() {
        return environment.getRequiredProperty("lightside.data_directory") + "/"
                + sysUserSvc.getSystemUser().get().getEmail();
    }

    @RequestMapping(value = "/action/database/{databaseName}/exportBratItem", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingBratExportResource>> exportBratActionItem(
            @PathVariable("databaseName") String databaseName,
            @RequestParam(value = "exportDirectory") String exportDirectory,
            @RequestParam(value = "dpId") long dpId, HttpServletRequest hsr, HttpSession session)
            throws IOException {
        registerDb(hsr, session, databaseName);

        Assert.hasText(exportDirectory, "No exportDirectory name specified");

        String bratDataDirectory = bratDataDirectory();
        DiscoursePart childDp = discoursePartRepository.findOne(dpId).get();

        String bratDirectory = bratDataDirectory + "/" + sanitize(exportDirectory);
        logger.info(" Exporting dp " + childDp.getName() + " in BRAT directory " + bratDirectory);
        exportDiscoursePartRecursively(childDp, bratDirectory, new HashSet<DiscoursePart>());
        return bratExports(databaseName, hsr, session);
    }

    // TODO: MOVE THIS METHOD TO BRAT SERVICE
    Set<DiscoursePart> exportDiscoursePartRecursively(DiscoursePart dp, String bratDirectory,
            Set<DiscoursePart> exported) throws IOException {
        if (exported.contains(dp)) {
            return exported;
        }

        Set<DiscoursePart> kids = discoursePartRelationRepository.findAllBySource(dp).stream()
                .map(dpr -> dpr.getTarget()).collect(Collectors.toSet());
        //logger.info("Recursive export: " + dp.getId() + " contains " + kids.size() + " kids");
        kids.removeAll(exported);
        //logger.info("Recursive export: " + dp.getId() + " contains " + kids.size() + " NEW kids");

        Set<DiscoursePart> exportedNow = exported;

        //NB: used to do this only if len(kids) == 0; but sometimes DPs can contain contributions *and* other DPs
        bratService.exportDiscoursePart(dp, bratDirectory, true);
        exportedNow.add(dp);
        logger.info("Recursive export: " + dp.getId() + " contains " + kids.size() + " NEW kids");
        for (DiscoursePart k : kids) {
            String kidname = bratService.discoursePart2BratName(dp);
            //dp.getClass().getAnnotation(Table.class).name() + "_"+dp.getId();
            //delete me

            //logger.info("About to recurse: kidname = " + kidname + " filename = " + (new File(bratDirectory,kidname)).toString());
            exportedNow.addAll(exportDiscoursePartRecursively(k,
                    (new File(bratDirectory, kidname + "_")).toString(), exportedNow));
        }
        return exportedNow;
    }

    @RequestMapping(value = "/action/database/{databaseName}/importBrat", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingBratExportResource>> importBratAction(
            @PathVariable("databaseName") String databaseName,
            @RequestParam(value = "bratDirectory") String bratDirectory, HttpServletRequest hsr,
            HttpSession session) throws IOException {
        registerDb(hsr, session, databaseName);

        String bratDataDirectory = bratDataDirectory();

        importBratRecursively(bratDataDirectory + "/" + bratDirectory);
        return bratExports(databaseName, hsr, session);
        /*
        bratService.importDataset(bratDataDirectory + "/" + bratDirectory);
        Optional<DiscoursePart> dp = bratName2DiscoursePart(bratDirectory);
        //if (dp.isPresent()) {
        //   return subDiscourseParts(0,20, dp.get().getId());
        //} else {
           return bratExports();
        //}*/
    }

    private void importBratRecursively(String directory) throws IOException {
        bratService.importDataset(directory);
        File dir = new File(directory);
        for (File s : dir.listFiles()) {
            if (s.isDirectory()) {
                importBratRecursively(s.toString());
            }
        }
    }

    @RequestMapping(value = "/database/{databaseName}/lightsideExports", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingLightsideStubsResource>> lightsideExports(
            @PathVariable("databaseName") String databaseName, HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);

        String lightsideDataDirectory = lsDataDirectory();
        List<BrowsingLightsideStubsResource> exported = BrowsingLightsideStubsResource
                .findPreviouslyExportedLightside(lightsideDataDirectory);
        Page<BrowsingLightsideStubsResource> p = new PageImpl<BrowsingLightsideStubsResource>(exported,
                new PageRequest(0, 100), exported.size());
        for (BrowsingLightsideStubsResource ltstub : p) {
            ltstub.add(makeLink1Arg("/browsing/action/database/" + databaseName + "/deleteLightside",
                    "Delete this export", "exportFilename", ltstub.getName()));
            ltstub.add(makeLightsideDownloadLink("/browsing/action/database/" + databaseName + "/downloadLightside",
                    ltstub.isAnnotated(), "Download", "exportFilename", ltstub.getName()));
        }
        PagedResources<Resource<BrowsingLightsideStubsResource>> ret = praLSAssembler.toResource(p);
        ret.add(makeLink(
                "/browsing/action/database/" + databaseName + "/uploadLightside{?file_annotatedFileForUpload}",
                "Upload"));
        return ret;
    }

    @RequestMapping(value = "/database/{databaseName}/bratExports", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingBratExportResource>> bratExports(
            @PathVariable("databaseName") String databaseName, HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);

        String who = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
        String bratDataDirectory = bratDataDirectory();
        List<BrowsingBratExportResource> exported = BrowsingBratExportResource
                .findPreviouslyExportedBrat(bratDataDirectory);
        Page<BrowsingBratExportResource> p = new PageImpl<BrowsingBratExportResource>(exported,
                new PageRequest(0, 100), exported.size());
        for (BrowsingBratExportResource bber : p) {
            bber.add(makeLink1Arg("/browsing/action/database/" + databaseName + "/importBrat", "Import BRAT markup",
                    "bratDirectory", bber.getName()));
            bber.add(makeBratLink("/index.xhtml#/" + who + "/" + bber.getName(), "Edit BRAT markup"));
            bber.add(makeLink1Arg("/browsing/action/database/" + databaseName + "/deleteBrat", "Delete BRAT markup",
                    "bratDirectory", bber.getName()));
        }
        return praBratAssembler.toResource(p);
    }

    @RequestMapping(value = "/database/{databaseName}/subDiscourseParts/{childOf}", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingDiscoursePartResource>> subDiscourseParts(
            @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "20") int size,
            @PathVariable("databaseName") String databaseName, @PathVariable("childOf") Long dpId,
            HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);

        PageRequest p = new PageRequest(page, size, new Sort("startTime"));

        Optional<DiscoursePart> parent = discoursePartRepository.findOne(dpId);
        if (parent.isPresent()) {
            Page<BrowsingDiscoursePartResource> repoResources = discoursePartRelationRepository
                    .findAllTargetsBySource(parent.get(), p)
                    /*.map(dpr -> dpr.getTarget())*/.map(dp -> new BrowsingDiscoursePartResource(dp, annoService));

            repoResources
                    .forEach(bdp -> bdp.fillInUserInteractions(discoursePartInteractionRepository, annoService));
            repoResources.forEach(bcr -> {
                if (bcr.getContainingDiscourseParts().size() > 1) {
                    bcr._getContainingDiscourseParts().forEach((childDpId, childDpName) -> {
                        bcr.add(makeLink("/browsing/database/" + databaseName + "/subDiscourseParts/" + childDpId,
                                "Contained in: " + childDpName));
                    });
                }
                //if (bcr.getContributionCount() > 0) {
                Link check3 = makeBratExportNameLink(
                        "/browsing/action/database/" + databaseName + "/exportBratItem", "chk:Export to BRAT",
                        parent.get().getName(), Long.toString(bcr._getDpId()));
                bcr.add(check3);
                //} 
                //Link check = makeLink1Arg("/browsing/action/exportLightside","chk:Export to Lightside: no annotations", "parentDpId", Long.toString(bcr._getDpId()));
                //Link check2 = makeLink2Arg("/browsing/action/exportLightside","chk:Export to Lightside: with annotations", "withAnnotations", "true", "parentDpId", Long.toString(bcr._getDpId()));
                Link check = makeLightsideExportNameLink(
                        "/browsing/action/database/" + databaseName + "/exportLightside", false,
                        "chk:Export to Lightside, no annotations", bcr.getName(), Long.toString(bcr._getDpId()));
                Link check2 = makeLightsideExportNameLink(
                        "/browsing/action/database/" + databaseName + "/exportLightside", true,
                        "chk:Export to Lightside, with annotations", bcr.getName(), Long.toString(bcr._getDpId()));
                bcr.add(check);
                bcr.add(check2);

            });
            PagedResources<Resource<BrowsingDiscoursePartResource>> response = praDiscoursePartAssembler
                    .toResource(repoResources);

            /*
             * Disabling exportBrat link overall -- instead add for each thread item as a checkbox
             *
            long threadcount = 0L;
            for (BrowsingDiscoursePartResource bdpr: repoResources) {
               threadcount += bdpr.getContributionCount();
            }
                
            if (threadcount > 0) {
               response.add(makeLink1Arg("/browsing/action/exportBrat", "Export these all to BRAT", "parentDpId", dpId.toString()));
            }
             */
            response.add(
                    makeLink("/browsing/database/" + databaseName + "/usersInDiscoursePart/" + dpId, "show users"));

            return response;
        } else {
            logger.info("subdiscourseParts(" + dpId + ") : isPresent==false");
            return null;
        }
    }

    @RequestMapping(value = "/database/{databaseName}/usersInDiscoursePart/{dpId}", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingUserResource>> users(@RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "20") int size,
            @PathVariable("databaseName") String databaseName, @PathVariable("dpId") Long dpId,
            HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);

        PageRequest p = new PageRequest(page, size);

        Optional<DiscoursePart> parent = discoursePartRepository.findOne(dpId);

        if (parent.isPresent()) {
            List<User> lbcr1 = new ArrayList<User>();
            lbcr1.addAll(userService.findUsersUnderDiscoursePart(parent.get()));
            List<BrowsingUserResource> lbcr = lbcr1.subList(page * size, lbcr1.size()).stream()
                    .map(BrowsingUserResource::new).map(b -> {
                        b.fillInDiscoursePartLinks(discoursePartService);
                        return b;
                    }).collect(Collectors.toList());

            Page<BrowsingUserResource> pbcr = new PageImpl<BrowsingUserResource>(lbcr, p, lbcr.size());
            PagedResources<Resource<BrowsingUserResource>> response = praUserAssembler.toResource(pbcr);

            return response;
        } else {
            return null;
        }
    }

    /*@RequestMapping(value = "/discourses", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingContributionResource>> discourses()  {
           
           
       List<Discourse> ds = discourseRepository.findAll();
     Page<BrowsingDiscourseResource> lbcr = 
           ds.map(BrowsingDiscourseResource::new);
     lbcr.forEach(bcr -> {if (bcr.getDiscourseParts().size() > 1) { bcr._getDiscourseParts().forEach(
                (dpId2, dpName2) -> {
                   if (dpId2 != dpId) { bcr.add(
                     makeLink("/browsing/dpContributions/" + dpId2 , "Also in:" + dpName2)); }}  ); }} );
     PagedResources<Resource<BrowsingDiscourseResource>> response = praContributionAssembler.toResource(lbcr);
         
     //response.add(makeLink("/browsing/subDiscourseParts{?page,size,repoType,annoType}", "search"));
     response.add(makeLink("/browsing/usersInDiscoursePart/" + dpId, "show users"));
     return response;
       } else {
     return null;
       }
    }*/

    @RequestMapping(value = "/database/{databaseName}/dpContributions/{childOf}", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingContributionResource>> dpContributions(
            @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "20") int size,
            @PathVariable("databaseName") String databaseName, @PathVariable("childOf") Long dpId,
            HttpServletRequest hsr, HttpSession session) {
        registerDb(hsr, session, databaseName);

        PageRequest p = new PageRequest(page, size, new Sort("startTime"));

        Optional<DiscoursePart> parent = discoursePartRepository.findOne(dpId);
        if (parent.isPresent()) {
            Page<BrowsingContributionResource> lbcr = discoursePartContributionRepository
                    .findByDiscoursePartSorted(parent.get(), p).map(dpc -> dpc.getContribution())
                    .map(c -> new BrowsingContributionResource(c, annoService));
            lbcr.forEach(bcr -> {
                if (bcr.getDiscourseParts().size() > 1) {
                    bcr._getDiscourseParts().forEach((dpId2, dpName2) -> {
                        if (dpId2 != dpId) {
                            bcr.add(makeLink("/browsing/database/" + databaseName + "/dpContributions/" + dpId2,
                                    "Also in:" + dpName2));
                        }
                    });
                }
            });
            PagedResources<Resource<BrowsingContributionResource>> response = praContributionAssembler
                    .toResource(lbcr);

            //response.add(makeLink("/browsing/subDiscourseParts{?page,size,repoType,annoType}", "search"));
            response.add(
                    makeLink("/browsing/database/" + databaseName + "/usersInDiscoursePart/" + dpId, "show users"));
            return response;
        } else {
            return null;
        }
    }

    /* TO DO:
     *     /prop_get  :  (pname, ptype) -> pvalue
     */

    /* Set of endpoints for manipulating system_user properties
    *
    */
    @RequestMapping(value = "/prop_list", method = RequestMethod.GET)
    @ResponseBody
    String prop_list(@RequestParam(value = "ptype", defaultValue = "*") String ptype, HttpServletRequest hsr,
            HttpSession session) {
        logger.info("Authenticating /prop_list");
        securityUtils.authenticate(hsr, session);
        logger.info("Authenticated /prop_list");

        logger.info("Requested property list of type " + ptype + " for user "
                + SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());

        List<SystemUserProperty> props = systemUserService.getPropertyList(ptype);
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(props);
        } catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            logger.info("Could not output property list!");
            e.printStackTrace();
            return "";
        }
    }

    @RequestMapping(value = "/prop_add", method = RequestMethod.POST)
    @ResponseBody
    String prop_add(@RequestParam(value = "ptype") String ptype, @RequestParam(value = "pname") String pname,
            @RequestParam(value = "pvalue") String pvalue, HttpServletRequest hsr, HttpSession session) {
        logger.info("Authenticating /prop_add");
        securityUtils.authenticate(hsr, session);
        logger.info("Authenticated /prop_add");

        logger.info("Creating property of type " + ptype + " named " + pname + " value= string of length "
                + Integer.toString(pvalue.length()) + " for user "
                + SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());

        systemUserService.createProperty(ptype, pname, pvalue);
        return prop_list(ptype, hsr, session);
    }

    // Note: the page/size parameters have different names here: "start" and "length" to make it
    // play well with a convenient Javascript component, dataTables per
    // documentation here: https://datatables.net/manual/server-side
    // Unfortunately it's not very configurable for things like this.
    @RequestMapping(value = "/query", method = RequestMethod.GET)
    @ResponseBody
    PagedResources<Resource<BrowsingContributionResource>> query(
            @RequestParam(value = "start", defaultValue = "0") int startposn,
            @RequestParam(value = "length", defaultValue = "20") int size, @RequestParam("query") String query,
            HttpServletRequest hsr, HttpSession session) {
        logger.info("Authenticating /query");
        securityUtils.authenticate(hsr, session);
        logger.info("Authenticated /query");

        int page = (startposn / size);
        PageRequest p = new PageRequest(page, size, new Sort("startTime"));

        try {
            logger.info("Got query " + query + "    page=" + Integer.toString(page) + "  size="
                    + Integer.toString(size));

            DdbQuery q = new DdbQuery(selector, discoursePartService, query);
            Page<BrowsingContributionResource> lbcr = q.retrieveAllContributions(Optional.empty(), p)
                    .map(c -> new BrowsingContributionResource(c, annoService));

            PagedResources<Resource<BrowsingContributionResource>> response = praContributionAssembler
                    .toResource(lbcr);

            return response;
        } catch (DdbQueryParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }

    }

    public Link makeBratLink(String urlend, String rel) {
        String brat_base = environment.getProperty("brat.ui_base");
        if (brat_base != null) {
            return (new Link(brat_base + urlend, rel));
        } else {
            return null;
        }
    }

    public static Link makeLink1Arg(String dest, String rel, String key, String value) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //.fromCurrentRequestUri()
                .replacePath(dest).replaceQueryParam(key, URLEncoder.encode(value)).build().toUriString();
        Link link = new Link(path, rel);
        return link;
    }

    public static Link makeLink2Arg(String dest, String rel, String key, String value, String key2, String value2) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //.fromCurrentRequestUri()
                .replacePath(dest).replaceQueryParam(key, URLEncoder.encode(value))
                .replaceQueryParam(key2, URLEncoder.encode(value2)).build().toUriString();
        Link link = new Link(path, rel);
        return link;
    }

    public static Link makeLightsideExportNameLink(String dest, Boolean withAnnotations, String rel,
            String filename, String dpid) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //.fromCurrentRequestUri()
                .replacePath(dest).replaceQueryParam("dpId", URLEncoder.encode(dpid))
                .replaceQueryParam("withAnnotations", withAnnotations).build().toUriString();
        Link link = new Link(path + "{?exportFilename}", rel);
        return link;
    }

    public static Link makeLightsideDownloadLink(String dest, Boolean annotated, String rel, String key,
            String value) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //.fromCurrentRequestUri()
                .replacePath(dest + "/" + value + ".csv")
                .replaceQueryParam("withAnnotations", annotated ? "true" : "false").build().toUriString();
        Link link = new Link(path, rel);
        return link;
    }

    public static Link makeBratExportNameLink(String dest, String rel, String filename, String dpid) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //fromCurrentRequestUri()
                .replacePath(dest).replaceQueryParam("dpId", URLEncoder.encode(dpid)).build().toUriString();
        Link link = new Link(path + "{?exportDirectory}", rel);
        return link;
    }

    public static Link makeLink(String dest, String rel) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping() //.fromCurrentRequestUri()
                .replacePath(dest).build().toUriString();
        Link link = new Link(path, rel);
        return link;
    }

    public static Link makePageLink(int page, int size, String rel) {
        String path = ServletUriComponentsBuilder.fromCurrentServletMapping().replaceQueryParam("page", page)
                .replaceQueryParam("size", size).build().toUriString();
        Link link = new Link(path, rel);
        return link;
    }
}