Java tutorial
/** * Licensed to the Sakai Foundation (SF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The SF 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.sakaiproject.nakamura.meservice; import static org.sakaiproject.nakamura.api.connections.ConnectionState.ACCEPTED; import static org.sakaiproject.nakamura.api.connections.ConnectionState.INVITED; import static org.sakaiproject.nakamura.api.connections.ConnectionState.PENDING; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.sling.SlingServlet; import org.apache.jackrabbit.util.ISO9075; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; import org.apache.sling.api.wrappers.ValueMapDecorator; import org.apache.sling.commons.json.JSONException; import org.apache.solr.client.solrj.util.ClientUtils; import org.sakaiproject.nakamura.api.connections.ConnectionConstants; import org.sakaiproject.nakamura.api.connections.ConnectionManager; import org.sakaiproject.nakamura.api.doc.BindingType; import org.sakaiproject.nakamura.api.doc.ServiceBinding; import org.sakaiproject.nakamura.api.doc.ServiceDocumentation; import org.sakaiproject.nakamura.api.doc.ServiceMethod; import org.sakaiproject.nakamura.api.doc.ServiceParameter; import org.sakaiproject.nakamura.api.doc.ServiceResponse; import org.sakaiproject.nakamura.api.lite.Session; import org.sakaiproject.nakamura.api.lite.StorageClientException; import org.sakaiproject.nakamura.api.lite.StorageClientUtils; import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException; import org.sakaiproject.nakamura.api.lite.authorizable.Authorizable; import org.sakaiproject.nakamura.api.lite.authorizable.AuthorizableManager; import org.sakaiproject.nakamura.api.lite.authorizable.Group; import org.sakaiproject.nakamura.api.message.LiteMessagingService; import org.sakaiproject.nakamura.api.message.MessagingException; import org.sakaiproject.nakamura.api.messagebucket.MessageBucketException; import org.sakaiproject.nakamura.api.messagebucket.MessageBucketService; import org.sakaiproject.nakamura.api.profile.ProfileService; import org.sakaiproject.nakamura.api.search.solr.Query; import org.sakaiproject.nakamura.api.search.solr.Result; import org.sakaiproject.nakamura.api.search.solr.SolrSearchException; import org.sakaiproject.nakamura.api.search.solr.SolrSearchResultSet; import org.sakaiproject.nakamura.api.search.solr.SolrSearchServiceFactory; import org.sakaiproject.nakamura.api.user.BasicUserInfoService; import org.sakaiproject.nakamura.api.user.UserConstants; import org.sakaiproject.nakamura.util.ExtendedJSONWriter; import org.sakaiproject.nakamura.util.LitePersonalUtils; import org.sakaiproject.nakamura.util.PathUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import javax.jcr.RepositoryException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; @ServiceDocumentation(name = "MeServlet", okForVersion = "1.1", shortDescription = "Returns information about the current active user.", description = "Presents information about current user in JSON format.", bindings = @ServiceBinding(type = BindingType.PATH, bindings = "/system/me"), methods = @ServiceMethod(name = "GET", description = "Get information about current user.", parameters = { @ServiceParameter(name = "uid", description = "If present the user id of the me feed to be returned") }, response = { @ServiceResponse(code = 200, description = "Request for information was successful. <br />" + "A JSON representation of the current user is returned. E.g. for an anonymous user:" + "<pre>{\n" + " \"user\": {\n" + " \"anon\": true,\n" + " \"subjects\": [],\n" + " \"superUser\": false\n" + " },\n" + " \"eventbus\": \"http://localhost:8080/system/uievent/default?token=YW5vbnltb3VzOzEzMGYzMmU3NDM3O2RlZmF1bHQ7ZXdLeUlvQ3phUnNXRlBXMHFyVFlsKzFQVkMwPQ&server=2324-Zachs-Mac.local&user=anonymous\",\n" + " \"profile\": {\n" + " \"basic\": {\n" + " \"access\": \"everybody\",\n" + " \"elements\": {\n" + " \"lastName\": {\n" + " \"value\": \"User\"\n" + " },\n" + " \"email\": {\n" + " \"value\": \"anon@sakai.invalid\"\n" + " },\n" + " \"firstName\": {\n" + " \"value\": \"Anonymous\"\n" + " }\n" + " }\n" + " },\n" + " \"rep:userId\": \"anonymous\"\n" + " },\n" + " \"messages\": {\n" + " \"unread\": 0\n" + " },\n" + " \"contacts\": {},\n" + " \"groups\": []\n" + "}<pre>"), @ServiceResponse(code = 401, description = "Unauthorized: credentials provided were not acceptable to return information for."), @ServiceResponse(code = 500, description = "Unable to return information about current user.") })) @SlingServlet(paths = { "/system/me" }, generateComponent = true, generateService = true, methods = { "GET" }) public class LiteMeServlet extends SlingSafeMethodsServlet { private static final long serialVersionUID = -3786472219389695181L; private static final Logger LOG = LoggerFactory.getLogger(LiteMeServlet.class); private static final String LOCALE_FIELD = "locale"; private static final String TIMEZONE_FIELD = "timezone"; @Reference protected transient LiteMessagingService messagingService; @Reference protected transient ConnectionManager connectionManager; @Reference protected transient ProfileService profileService; @Reference private MessageBucketService messageBucketService; @Reference SolrSearchServiceFactory searchServiceFactory; @Reference BasicUserInfoService basicUserInfoService; /** * {@inheritDoc} * * @see org.apache.sling.api.servlets.SlingSafeMethodsServlet#doGet(org.apache.sling.api.SlingHttpServletRequest, * org.apache.sling.api.SlingHttpServletResponse) */ @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { try { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); javax.jcr.Session jcrSession = request.getResourceResolver().adaptTo(javax.jcr.Session.class); final Session session = StorageClientUtils .adaptToSession(request.getResourceResolver().adaptTo(javax.jcr.Session.class)); if (session == null) { LOG.error("########## session is null"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access denied error."); return; } AuthorizableManager um = session.getAuthorizableManager(); String userId = session.getUserId(); String requestedUserId = request.getParameter("uid"); if (requestedUserId != null && requestedUserId.length() > 0) { userId = requestedUserId; } Authorizable au = um.findAuthorizable(userId); if (au == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "User " + userId + " not found."); return; } PrintWriter w = response.getWriter(); ExtendedJSONWriter writer = new ExtendedJSONWriter(w); writer.object(); // User info writer.key("user"); writeUserJSON(writer, session, au, request); try { String messageBucketUrl = messageBucketService.getBucketUrl(request, "default"); if (messageBucketUrl != null) { writer.key("eventbus"); writer.value(messageBucketUrl); } } catch (MessageBucketException e) { LOG.warn("Failed to create message bucket URL {} " + e.getMessage()); LOG.debug("Failed to create message bucket URL {} " + e.getMessage(), e); } // Dump this user his info writer.key("profile"); ValueMap profile = profileService.getProfileMap(au, jcrSession); writer.valueMap(profile); // Dump this user his number of unread messages. writer.key("messages"); writeMessageCounts(writer, session, au, request); // Dump this user his number of contacts. writer.key("contacts"); writeContactCounts(writer, session, au, request); // Dump the groups for this user. writer.key("groups"); writeGroups(writer, session, au, jcrSession); writer.endObject(); } catch (JSONException e) { LOG.error("Failed to create proper JSON response in /system/me", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create proper JSON response."); } catch (StorageClientException e) { LOG.error("Failed to get a user his profile node in /system/me", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Sparse storage client error."); } catch (AccessDeniedException e) { LOG.error("Failed to get a user his profile node in /system/me", e); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access denied error."); } catch (RepositoryException e) { LOG.error("Failed to get a user his profile node in /system/me", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Sparse storage client error."); } catch (MessagingException e) { LOG.error("Failed to get a user his message counts in /system/me", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Messaging error."); } catch (SolrSearchException e) { LOG.error("Failed to execute a Solr search in /system/me", e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Solr search error."); } } /** * @param writer * @param session * @param au * @param jcrSession * @throws JSONException * @throws StorageClientException * @throws AccessDeniedException * @throws RepositoryException */ protected void writeGroups(ExtendedJSONWriter writer, Session session, Authorizable au, javax.jcr.Session jcrSession) throws JSONException, StorageClientException, AccessDeniedException { AuthorizableManager authorizableManager = session.getAuthorizableManager(); writer.array(); if (!UserConstants.ANON_USERID.equals(au.getId())) { // KERN-1831 changed from getPrincipals to memberOf to drill down list for (Iterator<Group> memberOf = au.memberOf(authorizableManager); memberOf.hasNext();) { // this is the old code for outputting only direct memberships. might be needed later if such a flag is added. // String[] principals = au.getPrincipals(); // for(String principal : principals) { // Authorizable group = authorizableManager.findAuthorizable(principal); Authorizable group = memberOf.next(); if (group == null || !(group instanceof Group) || Group.EVERYONE.equals(group.getId())) { // we don't want the "everyone" group in this feed continue; } if (group.hasProperty(UserConstants.PROP_MANAGED_GROUP)) { // fetch the group that the manager group manages group = authorizableManager .findAuthorizable((String) group.getProperty(UserConstants.PROP_MANAGED_GROUP)); if (group == null || !(group instanceof Group)) { continue; } } ValueMap groupProfile = new ValueMapDecorator(basicUserInfoService.getProperties(group)); if (groupProfile != null) { writer.valueMap(groupProfile); } } } writer.endArray(); } /** * Writes a JSON Object that contains the number of contacts for a user split up in * PENDING, ACCEPTED. * * @param writer * @param session * @param au * @throws JSONException * @throws SolrSearchException * @throws RepositoryException */ protected void writeContactCounts(ExtendedJSONWriter writer, Session session, Authorizable au, SlingHttpServletRequest request) throws JSONException, SolrSearchException { writer.object(); // We don't do queries for anonymous users. (Possible ddos hole). String userID = au.getId(); if (UserConstants.ANON_USERID.equals(userID)) { writer.endObject(); return; } // Get the path to the store for this user. Map<String, Integer> contacts = new HashMap<String, Integer>(); contacts.put(ACCEPTED.toString().toLowerCase(), 0); contacts.put(INVITED.toString().toLowerCase(), 0); contacts.put(PENDING.toString().toLowerCase(), 0); try { // This could just use ConnectionUtils.getConnectionPathBase, but that util class is // in the private package unfortunately. String store = LitePersonalUtils.getHomePath(userID) + "/" + ConnectionConstants.CONTACT_STORE_NAME; store = ISO9075.encodePath(store); String queryString = "path:" + ClientUtils.escapeQueryChars(store) + " AND resourceType:sakai/contact AND state:(ACCEPTED OR INVITED OR PENDING)"; Query query = new Query(queryString); LOG.debug("Submitting Query {} ", query); SolrSearchResultSet resultSet = searchServiceFactory.getSearchResultSet(request, query, false); Iterator<Result> resultIterator = resultSet.getResultSetIterator(); while (resultIterator.hasNext()) { Result contact = resultIterator.next(); if (contact.getProperties().containsKey("state")) { String state = (String) contact.getProperties().get("state").iterator().next(); int count = 0; if (contacts.containsKey(state)) { count = contacts.get(state); } contacts.put(state, count + 1); } } } finally { for (Entry<String, Integer> entry : contacts.entrySet()) { writer.key(entry.getKey()); writer.value(entry.getValue()); } } writer.endObject(); } /** * Writes a JSON Object that contains the unread messages for a user. * * @param writer * The writer * @param session * A JCR session to perform queries with. This session needs read access on the * authorizable's message box. * @param au * An authorizable to look up the messages for. * @throws JSONException * @throws RepositoryException * @throws MessagingException * @throws SolrSearchException */ protected void writeMessageCounts(ExtendedJSONWriter writer, Session session, Authorizable au, SlingHttpServletRequest request) throws JSONException, MessagingException, SolrSearchException { writer.object(); writer.key("unread"); // We don't do queries for anonymous users. (Possible ddos hole). String userID = au.getId(); if (UserConstants.ANON_USERID.equals(userID)) { writer.value(0); writer.endObject(); return; } long count = 0; try { String store = messagingService.getFullPathToStore(au.getId(), session); store = ISO9075.encodePath(store); store = store.substring(0, store.length() - 1); String queryString = "path:" + ClientUtils.escapeQueryChars(store) + " AND resourceType:sakai/message AND type:internal AND messagebox:inbox AND read:false"; Query query = new Query(queryString); LOG.debug("Submitting Query {} ", query); SolrSearchResultSet resultSet = searchServiceFactory.getSearchResultSet(request, query, false); count = resultSet.getSize(); } finally { writer.value(count); } writer.endObject(); } /** * * @param write * @param session * @param authorizable * @throws RepositoryException * @throws JSONException * @throws StorageClientException */ protected void writeUserJSON(ExtendedJSONWriter write, Session session, Authorizable authorizable, SlingHttpServletRequest request) throws JSONException, StorageClientException { String user = session.getUserId(); boolean isAnonymous = (UserConstants.ANON_USERID.equals(user)); if (isAnonymous || authorizable == null) { write.object(); write.key("anon").value(true); write.key("subjects"); write.array(); write.endArray(); write.key("superUser"); write.value(false); write.endObject(); } else { Set<String> subjects = getSubjects(authorizable, session.getAuthorizableManager()); Map<String, Object> properties = getProperties(authorizable); write.object(); writeGeneralInfo(write, authorizable, subjects, properties); writeLocale(write, properties, request); write.endObject(); } } /** * Writes the local and timezone information. * * @param write * @param properties * @throws JSONException */ protected void writeLocale(ExtendedJSONWriter write, Map<String, Object> properties, SlingHttpServletRequest request) throws JSONException { /* Get the correct locale */ Locale l = request.getLocale(); if (properties.containsKey(LOCALE_FIELD)) { String locale[] = properties.get(LOCALE_FIELD).toString().split("_"); if (locale.length == 2) { l = new Locale(locale[0], locale[1]); } } /* Get the correct time zone */ TimeZone tz = TimeZone.getDefault(); if (properties.containsKey(TIMEZONE_FIELD)) { String timezone = properties.get(TIMEZONE_FIELD).toString(); tz = TimeZone.getTimeZone(timezone); } int daylightSavingsOffset = tz.inDaylightTime(new Date()) ? tz.getDSTSavings() : 0; int offset = tz.getRawOffset() + daylightSavingsOffset; /* Add the locale information into the output */ write.key("locale"); write.object(); write.key("country"); write.value(l.getCountry()); write.key("displayCountry"); write.value(l.getDisplayCountry(l)); write.key("displayLanguage"); write.value(l.getDisplayLanguage(l)); write.key("displayName"); write.value(l.getDisplayName(l)); write.key("displayVariant"); write.value(l.getDisplayVariant(l)); write.key("ISO3Country"); write.value(l.getISO3Country()); write.key("ISO3Language"); write.value(l.getISO3Language()); write.key("language"); write.value(l.getLanguage()); write.key("variant"); write.value(l.getVariant()); /* Add the timezone information into the output */ write.key("timezone"); write.object(); write.key("name"); write.value(tz.getID()); write.key("GMT"); write.value(offset / 3600000); write.endObject(); write.endObject(); } /** * Writes the general information about a user such as the userid, storagePrefix, wether * he is a superUser or not.. * * @param write * @param user * @param session * @throws JSONException * @throws RepositoryException */ protected void writeGeneralInfo(ExtendedJSONWriter write, Authorizable user, Set<String> subjects, Map<String, Object> properties) throws JSONException { write.key("userid").value(user.getId()); write.key("userStoragePrefix"); // For backwards compatibility we substring the first slash out and append one at the // back. write.value("~" + user.getId() + "/"); write.key("userProfilePath"); write.value(PathUtils.translateAuthorizablePath(LitePersonalUtils.getProfilePath(user.getId()))); write.key("superUser"); write.value(subjects.contains("administrators")); write.key("properties"); ValueMap jsonProperties = new ValueMapDecorator(properties); write.valueMap(jsonProperties); write.key("subjects"); write.array(); for (String groupName : subjects) { write.value(groupName); } write.endArray(); } /** * All the names of the {@link Group groups} a user is a member of. * * @param authorizable * The {@link Authorizable authorizable} that represents the user. * @param authorizableManager * The {@link AuthorizableManager authorizableManager} that can be used to retrieve * the group membership. * @return All the names of the {@link Group groups} a user is a member of. * @throws RepositoryException */ protected Set<String> getSubjects(Authorizable authorizable, AuthorizableManager authorizableManager) { Set<String> subjects = new HashSet<String>(); if (authorizable != null) { String principal = authorizable.getId(); if (principal != null) { Iterator<Group> it = authorizable.memberOf(authorizableManager); while (it.hasNext()) { Group aGroup = it.next(); if (!aGroup.getId().equals(Group.EVERYONE)) { subjects.add(aGroup.getId()); } } } } return subjects; } private Map<String, Object> getProperties(Authorizable authorizable) { Map<String, Object> result = new HashMap<String, Object>(); if (authorizable != null) { for (String propName : authorizable.getSafeProperties().keySet()) { if (propName.startsWith("rep:")) continue; Object o = authorizable.getProperty(propName); if (o instanceof Object[]) { Object[] values = (Object[]) o; switch (values.length) { case 0: continue; case 1: result.put(propName, values[0]); break; default: { StringBuilder valueString = new StringBuilder(""); for (int i = 0; i < values.length; i++) { valueString.append("," + values[i]); } result.put(propName, valueString.toString().substring(1)); } } } else { result.put(propName, o); } } } return result; } }