public class CacheFilter extends Filter {

	public CacheFilter(Context context, Restlet restlet) {
		super(context, restlet);
	}

	/**
	 * Sets the HTTP Headers if request was successful
	 */
	protected void afterHandle(Request request, Response response) {
		super.afterHandle(request, response);
		if (response != null && response.getEntity() != null) {
			if (response.getStatus().equals(Status.SUCCESS_OK)) {
				final Calendar calendar = Calendar.getInstance();
				calendar.add(Calendar.YEAR, 1);
				response.getEntity().setExpirationDate(calendar.getTime());
				response.setCacheDirectives(new ArrayList<CacheDirective>());
				response.getCacheDirectives().add(CacheDirective.maxAge(31536000));
			}
		}
	}

} public class MetadataFetchResource extends SelfInjectingServerResource {

	@Inject
	IsOrcidWorkProvider orcidWorkProvider;

	/**
	 * Fetches a metadata record and returns it as an XML OrcidWork.
	 */
	@Get("xml")
	public Representation getMetadataAsOrcidWork() {
		try {
			//System.out.println(this.getAttribute("id"));
			//System.out.println(URLDecoder.decode(this.getQueryValue("id")));
			//IsOrcidWork meta = orcidWorkProvider.fetch(URLDecoder.decode(this.getAttribute("id")));
			IsOrcidWork meta = orcidWorkProvider.fetch(URLDecoder.decode(this.getQueryValue("id")));
			return new JaxbRepresentation<OrcidWork>(meta.toOrcidWork());
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e.getMessage());
			return null;
		}
	}

	/**
	 * Fetches a JSON representation of a metadata document Note this returns
	 * the RAW form of the object as JSON, not the ORCID Transformed form So
	 * depends on the underlying implementation of the class returned by the
	 * provider. This is useful for user confirmation etc.
	 */
	@Get("?json")
	public IsOrcidWork getEthosMetadata() throws IOException {
		try {
			return orcidWorkProvider.fetch(this.getAttribute("id"));
		} catch (IOException e) {
			// TODO: make this fine grained - non existent, bad request and
			// server error.
			this.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "problem fetching metadata " + e.getMessage());
			throw e;
		}
	}

} public class OrcidAuthURLResource extends SelfInjectingServerResource {

	@Inject
	OrcidOAuthClient orcidOAuthClient;

	/**
	 * Generates an authz request url to direct the user to ?redirect.true
	 * bounces user with a http redirect.
	 * 
	 * @return json {"url":"..."}
	 */
	@Get("json")
	public Map<String, String> getAuthzCodeRedirectURL() {
		String ref = this.getQueryValue("originalRef");
		String url = orcidOAuthClient.getAuthzCodeRequest(ref, OrcidAuthScope.CREATE_WORKS);
		if (this.getQueryValue("redirect") != null) {
			this.redirectPermanent(url);
			return null;
		}
		Map<String, String> m = new HashMap<String, String>();
		m.put("url", url);
		return m;
	}

} public class OrcidIdentifierResource extends SelfInjectingServerResource {

	@Get("json")
	public List<String> getIdentifierList() {
		List<String> ids = new ArrayList<String>();
		if (getAttribute("type").equals("external")) {
			for (OrcidExternalIdentifierType type : OrcidExternalIdentifierType.values()) {
				ids.add(type.toString());
			}
		} else if (getAttribute("type").equals("worktype")) {
			for (OrcidWorkType type : OrcidWorkType.values()) {
				ids.add(type.toString());
			}
		} else if (getAttribute("type").equals("searchfield")) {
			for (OrcidSearchField type : OrcidSearchField.values()) {
				ids.add(type.toString());
			}
		} else if (getAttribute("type").equals("searchtype")) {
			for (SearchType type : SearchType.values()) {
				ids.add(type.toString());
			}
		}
		if (ids.size() > 0)
			return ids;
		setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "invalid identifier type");
		return null;
	}

} public class OrcidProfileResource extends SelfInjectingServerResource {

	@Inject
	OrcidPublicClient client;

	@Get("xml")
	public Representation getProfile() {
		try {
			OrcidProfile profile = client.getOrcidProfile(this.getAttribute("orcid"));
			return new JaxbRepresentation<OrcidProfile>(profile);
		} catch (ResourceException e) {
			setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "invalid orcid, cannot resolve orcid");
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e.getMessage());
		}
		return null;
	}

} public class OrcidSearchResource extends SelfInjectingServerResource {

	private static final Logger log = Logger.getLogger(OrcidSearchResource.class.getName());

	@Inject
	OrcidPublicClient client;

	private SearchKey search = new SearchKey();

	/**
	 * Performs a search against the public API. Expects GET params: idtype -
	 * one of OrcidExternalIdentifierType, for example: isbn, doi, other-id term
	 * - the search term, searchtype - one of 'exact', 'solr' or 'prefix'
	 * Optional params: page (from 0), pagesize
	 * 
	 */
	@Override
	public void doInit() {
		super.doInit();// for injection!
		try {
			OrcidSearchField field = OrcidExternalIdentifierType.fromString(this.getQueryValue("idtype"))
					.toOrcidSearchField();
			String searchTerm = this.getQueryValue("term");
			if (searchTerm == null)
				throw new IllegalArgumentException("missing term parameter");
			String searchType = this.getQueryValue("searchtype");
			if (searchType == null || !(searchType.equals("exact") || searchType.equals("prefix")))
				throw new IllegalArgumentException("invalid search type.  Pick one of 'exact', 'solr' or 'prefix'");
			if (searchType.equals("exact"))
				search.setQuery(field.buildExactQuery(searchTerm));
			else if (searchType.equals("prefix"))
				search.setQuery(field.buildPrefixQuery(searchTerm));
			else if (searchType.equals("solr"))
				search.setQuery(field.buildSolrQuery(searchTerm));

			String page = this.getQueryValue("page");
			String pageSize = this.getQueryValue("pagesize");
			if (page != null)
				search.setPage(Integer.parseInt(page));
			if (pageSize != null)
				search.setPagesize(Integer.parseInt(pageSize));

		} catch (IllegalArgumentException e) {
			this.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e.getMessage());
		}
	}

	@Get("xml")
	public Representation search() {
		try {
			OrcidSearchResults result = client.search(search.getQuery(), search.getPage(), search.getPagesize());
			return new JaxbRepresentation<OrcidSearchResults>(result);
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e.getMessage());
			return null;
		}
	}

} public class OrcidTokenResource extends SelfInjectingServerResource {

	private static final Logger log = Logger.getLogger(OrcidTokenResource.class.getName());

	@Inject
	OrcidOAuthClient orcidOAuthClient;

	/**
	 * This resource is hit by the return from a ORCID OAuth. returns Access
	 * token to client along with state param.
	 */
	@Get("json")
	public OrcidAccessToken getTokenResponse() {
		String code = this.getQueryValue("code");
		try {
			OrcidAccessToken token = orcidOAuthClient.getAccessToken(code);
			String ref = this.getQueryValue("state");
			token.setState(ref);
			return token;
		} catch (ResourceException e) {
			if (e.getStatus().isServerError())
				this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e);
			else
				this.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e);
			log.info("Resource exception" + e.getMessage());
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e);
		}
		return null;
	}
} public class OrcidWorkCreationResource extends SelfInjectingServerResource {

	@SuppressWarnings("unused")
	private static final Logger log = Logger.getLogger(OrcidWorkCreationResource.class.getName());

	@Inject
	OrcidOAuthClient orcidOAuthClient;

	private static JAXBContext orcidWorkJAXBContext;// =
													// JAXBContext.newInstance(OrcidWork.class);

	@Override
	public void doInit() {
		super.doInit();
		if (orcidWorkJAXBContext == null) {
			try {
				orcidWorkJAXBContext = JAXBContext.newInstance(OrcidWork.class);
			} catch (JAXBException e) {
				this.setStatus(Status.SERVER_ERROR_INTERNAL, e.getMessage());
			}
		}
	}

	/**
	 * Accepts an XML encoded ORCID work. POSTs work to ORCID as a new work.
	 * Requires ?token= query param containing ORCID auth token.
	 * 
	 * @param rep
	 *            a serialised OrcidWork
	 * @throws IOException
	 *             if we can't append. (TODO: handle properly)
	 */
	@Post
	public void addWork(Representation rep) {
		try {
			Unmarshaller um = orcidWorkJAXBContext.createUnmarshaller();
			OrcidWork work = (OrcidWork) um.unmarshal(rep.getStream());
			OrcidAccessToken token = new OrcidAccessToken();
			token.setOrcid(this.getAttribute("orcid"));
			token.setAccess_token(this.getQueryValue("token"));
			orcidOAuthClient.appendWork(token, work);
			this.setStatus(Status.SUCCESS_NO_CONTENT);
		} catch (JAXBException e) {
			this.setStatus(Status.SERVER_ERROR_INTERNAL, e);
		} catch (ResourceException e) {
			e.printStackTrace();
			this.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e);
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e);
		}

		// TODO: test on GAE 1.8.1+
		// the following fails on GAE due to missing JAXB security feature :(
		// hence using manual jaxb above.
		/*
		 * JaxbRepresentation<OrcidWork> jaxbRep = new
		 * JaxbRepresentation<OrcidWork>(rep, OrcidWork.class); OricdOAuthClient
		 * oauth = new OricdOAuthClient(); String orcid =
		 * this.getAttribute("orcid"); String token =
		 * this.getQueryValue("token"); oauth.appendWork(orcid, token,
		 * jaxbRep.getObject()); this.setStatus(Status.SUCCESS_NO_CONTENT);
		 */
	}

} public class DatacentreDOIPrefixResource extends SelfInjectingServerResource {

	@Inject
	DOIPrefixMapper mapper;

	@Get("json")
	public Representation getPrefixMap() {
		return new JacksonRepresentation<List<TypeaheadJSBean>>(TypeaheadJSBean.fromMultimap(mapper.getDatacentreMap()));
	}

} public class OrcidDataCentreReportResource extends SelfInjectingServerResource {

	@SuppressWarnings("unused")
	private static final Logger log = Logger.getLogger(OrcidDataCentreReportResource.class.getName());

	@Inject
	OrcidPublicClient client;

	@Inject
	CacheManager cache;

	SearchKey search = new SearchKey();
	String sEcho;

	/**
	 * Parse the query. Expects datatable query params: iDisplayStart
	 * iDisplayLength sSearch sEcho
	 * 
	 * also optionally expects idtype (defaults to doi) searchtype (defaults to
	 * prefix)
	 * 
	 * @see http://datatables.net/usage/server-side
	 */
	@Override
	public void doInit() {
		super.doInit();// for injection!
		String iDisplayStart = this.getQueryValue("iDisplayStart");
		String iDisplayLength = this.getQueryValue("iDisplayLength");
		String sSearch = this.getQueryValue("sSearch");
		sEcho = this.getQueryValue("sEcho");

		OrcidSearchField field = OrcidSearchField.DIGITAL_OBJECT_IDS;
		if (this.getQueryValue("idtype") != null) {
			try {
				field = OrcidExternalIdentifierType.fromString(getQueryValue("idtype")).toOrcidSearchField();
			} catch (IllegalArgumentException e) {
				throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "invalid idtype");
			}
		}

		SearchType type = SearchType.PREFIX;
		if (getQueryValue("searchtype") != null) {
			try {
				type = SearchType.fromString(getQueryValue("searchtype"));
			} catch (IllegalArgumentException e) {
				throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "invalid searchtype");
			}
		}

		search.setQuery(field.buildQuery(type, sSearch));
		search.setPage(Integer.parseInt(iDisplayStart));
		search.setPagesize(Integer.parseInt(iDisplayLength));
	}

	/**
	 * Get a datatable representation of the search results. Suitable for use
	 * with http://datatables.net/ Caches results with provided CacheManager
	 */
	@Get("json")
	public Representation getDataTableRepresentaion() {
		try {
			OrcidSearchResults searchResults = cache.getSearchCache().getIfPresent(search);
			if (searchResults == null)
				searchResults = client.search(search.getQuery(), search.getPage(), search.getPagesize());
			cache.getSearchCache().put(search, searchResults);
			DatatableResults list = new DatatableResults();
			list.iTotalRecords = searchResults.getNumFound().intValue();
			list.iTotalDisplayRecords = searchResults.getNumFound().intValue();
			list.sEcho = Integer.parseInt(sEcho);
			Joiner joiner = Joiner.on(", ").skipNulls();
			for (OrcidSearchResult result : searchResults.getOrcidSearchResult()) {
				BriefResult briefResult = new BriefResult();
				final OrcidId identifier = result.getOrcidProfile().getOrcidIdentifier();
				briefResult.orcid = getValueFromOrcidId(identifier, "path");
				briefResult.link = "<a href='" + getValueFromOrcidId(identifier, "uri")
						+ "' target='_blank'>" + briefResult.orcid + "</a>";
				briefResult.name = joiner.join(result.getOrcidProfile().getOrcidBio().getPersonalDetails()
						.getFamilyName(), result.getOrcidProfile().getOrcidBio().getPersonalDetails().getGivenNames());
				list.aaData.add(briefResult);
			}
			return new JacksonRepresentation<DatatableResults>(list);
		} catch (IOException e) {
			this.setStatus(Status.SERVER_ERROR_BAD_GATEWAY, e.getMessage());
			return null;
		}
	}

	// http://datatables.net/release-datatables/examples/data_sources/server_side.html
	// http://datatables.net/release-datatables/examples/ajax/objects.html

	// this one
	// http://datatables.net/forums/discussion/16675/twitter-bootstrap-3
	public static class DatatableResults {
		public List<BriefResult> aaData = new ArrayList<BriefResult>();
		public int iTotalRecords = 0;
		public int iTotalDisplayRecords = 0;
		public int sEcho = 0;
	}

	public static class BriefResult {
		public String name;
		public String orcid;
		public String link;
	}

	private String getValueFromOrcidId(OrcidId orcidId, String name) throws IOException {
		for (JAXBElement<String> element : orcidId.getContent()) {
			if (element.getName().getLocalPart().equals(name)) {
				return element.getValue();
			}
		}

		throw new IOException("Unable to find JAXBElement with name '" + name + "'");
	}
} public class PublisherDOIPrefixResource extends SelfInjectingServerResource {

	@Inject
	DOIPrefixMapper mapper;

	@Get("json")
	public Representation getPrefixMap() {
		return new JacksonRepresentation<List<TypeaheadJSBean>>(TypeaheadJSBean.fromMultimap(mapper.getPublisherMap()));
	}

	public static class TypeaheadJSBean {
		private static Joiner joiner = Joiner.on('|');

		public TypeaheadJSBean(String publisher, String doi) {
			super();
			this.value = publisher;
			this.doi = doi;
		}

		public TypeaheadJSBean(String publishers, Collection<String> dois) {
			this.value = publishers;
			this.doi = joiner.join(dois);
		}

		public String value;
		public String doi;

		public static List<TypeaheadJSBean> fromMultimap(Multimap<String, String> m) {
			List<TypeaheadJSBean> list = new ArrayList<TypeaheadJSBean>();
			for (String s : m.keySet()) {
				list.add(new TypeaheadJSBean(s, m.get(s)));
			}
			return list;
		}
	}

}
import java.util.logging.Logger;
import org.restlet.Context;
import org.restlet.resource.Directory;
import org.restlet.routing.Filter;
import org.restlet.routing.Router;
import uk.bl.odin.orcid.rest.CacheFilter;
import uk.bl.odin.orcid.rest.MetadataFetchResource;
import uk.bl.odin.orcid.rest.OrcidAuthURLResource;
import uk.bl.odin.orcid.rest.OrcidIdentifierResource;
import uk.bl.odin.orcid.rest.OrcidProfileResource;
import uk.bl.odin.orcid.rest.OrcidSearchResource;
import uk.bl.odin.orcid.rest.OrcidTokenResource;
import uk.bl.odin.orcid.rest.OrcidWorkCreationResource;
import uk.bl.odin.orcid.rest.report.DatacentreDOIPrefixResource;
import uk.bl.odin.orcid.rest.report.OrcidDataCentreReportResource;
import uk.bl.odin.orcid.rest.report.PublisherDOIPrefixResource;
package uk.bl.odin.orcid;




/**
 * RESTlet routing and general application configuration.
 */
public class RootRouter extends Router {

	private static final Logger log = Logger.getLogger(RootRouter.class.getName());

	/**
	 * Configures endpoints, sets up OrcidOAuthClient & OrcidWorkProvider and
	 * places them in the RESTlet Context
	 * 
	 * Routes:
	 * <ul>
	 * <li>"/orcid/token" convert authz codes from orcid into authz tokens</li>
	 * <li>"/orcid/requests" generate a authz request url (?redirect=true to
	 * bounce user)</li>
	 * <li>"/orcid/requests/{originalRef}" generate a authz request url with
	 * originalRef as state param (?redirect=true to bounce user)</li>
	 * <li>"/orcid/{orcid}/orcid-works/create" create a work by posting
	 * OrcidWork XML (requires ?token= orcid oauth token)</li>
	 * <li>"/meta/{id}" fetch metadata from external source - use (?json) for
	 * raw form</li>
	 * <li>"/webjars" webjars endpoint - example:
	 * /webjars/bootstrap/3.0.3/css/bootstrap.min.css</li>
	 * </ul>
	 * 
	 * 
	 */
	public RootRouter(Context context) {
		super(context);

		// import work rest routes
		this.attach("/orcid/token", OrcidTokenResource.class);
		//this.attach("/orcid/requests/{originalRef}", OrcidAuthURLResource.class);
		this.attach("/orcid/requests", OrcidAuthURLResource.class);//?originalRef optional

		this.attach("/orcid/search", OrcidSearchResource.class);

		this.attach("/orcid/{orcid}/orcid-works/create", OrcidWorkCreationResource.class);
		this.attach("/orcid/{orcid}", OrcidProfileResource.class);
		this.attach("/meta", MetadataFetchResource.class); //the configured type