Java tutorial
/********************************************************************************** * $URL: $ * $Id: $ ********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.lessonbuildertool.service; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.net.URLEncoder; import java.util.Collection; import java.util.HashSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Locale; import java.util.List; import java.util.ArrayList; import java.util.regex.Pattern; import java.net.URL; import java.net.URLConnection; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.authz.api.AuthzGroupService; import org.sakaiproject.authz.api.AuthzGroup; import org.sakaiproject.authz.api.Member; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.SitePage; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.tool.api.Placement; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.user.api.User; import org.sakaiproject.user.cover.UserDirectoryService; import org.sakaiproject.util.Validator; import org.sakaiproject.util.Web; import org.springframework.context.MessageSource; import org.sakaiproject.util.FormattedText; import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean; import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao; import org.sakaiproject.lessonbuildertool.SimplePage; import org.sakaiproject.lessonbuildertool.SimplePageItem; /** * <p> * Ajax server * </p> * .../lessonbuilder-tool/ajax?op=getmessage&code=simplepage.new-page&locale=fr * look up text messages in messages.properties. "" if can't find it * this lets us avoid passing the text of some larger messages as text on * every HTML page. The server doesn't have enough context to know the right * locale for itself, but the UI should know it * .../lessonbuilder-tool/ajax?op=getmimetype&url=http://www.rutgers.edu * check a URL to see what MIME type it is, "" if can't find it * though the URL should actually be urlencoded * * @author Charles Hedrick, Rutgers University. * @version $Revision: $ */ @SuppressWarnings({ "serial", "deprecation" }) public class AjaxServer extends HttpServlet { private static final String UTF8 = "UTF-8"; private static MessageSource messageSource; private static SiteService siteService; private static AuthzGroupService authzGroupService; private static SimplePageToolDao simplePageToolDao; public void setSimplePageToolDao(Object dao) { System.out.println("setdao " + dao); simplePageToolDao = (SimplePageToolDao) dao; } /** Our log (commons). */ private static Log log = LogFactory.getLog(AjaxServer.class); public static final String FILTERHTML = "lessonbuilder.filterhtml"; private static String filterHtml = ServerConfigurationService.getString(FILTERHTML); static Class levelClass = null; static Object[] levels = null; static Class ftClass = null; static Method ftMethod = null; static Object ftInstance = setupFtStuff(); static Object setupFtStuff() { Object ret = null; try { levelClass = Class.forName("org.sakaiproject.util.api.FormattedText$Level"); levels = levelClass.getEnumConstants(); ftClass = Class.forName("org.sakaiproject.util.api.FormattedText"); ftMethod = ftClass.getMethod("processFormattedText", new Class[] { String.class, StringBuilder.class, levelClass }); ret = org.sakaiproject.component.cover.ComponentManager.get("org.sakaiproject.util.api.FormattedText"); return ret; } catch (Exception e) { log.error("Formatted Text with levels not available: " + e); return null; } } /** * Access the Servlet's information display. * * @return servlet information. */ public String getServletInfo() { return "Lessons Ajax Server"; } /** * Initialize the servlet. * * @param config * The servlet config. * @throws ServletException */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * Shutdown the servlet. */ public void destroy() { log.info("destroy()"); super.destroy(); } /** * Respond to Get requests: * display main content by redirecting to it and adding * user= euid= site= role= serverurl= time= sign= * for privileged users, add a bar at the top with a link to * the setup screen * ?Setup generates the setup screen * * @param req * The servlet request. * @param res * The servlet response. * @throws ServletException. * @throws IOException. */ public static String getMessage(String code, String localeName) { Locale locale = null; if (localeName != null && localeName.length() > 0) { String langLoc[] = localeName.split("_"); if (langLoc.length >= 2) { if ("en".equals(langLoc[0]) && "ZA".equals(langLoc[1])) { locale = new Locale("en", "GB"); } else { locale = new Locale(langLoc[0], langLoc[1]); } } else { locale = new Locale(langLoc[0]); } } if (locale == null) locale = Locale.getDefault(); String value = ""; try { value = messageSource.getMessage(code, new String[0], locale); } catch (Exception e) { } ; return value; } // in order to match the logic in ShowPageProducer // * youtube has to return something other than html, in this case application/youtube, // * image types have to return something with image, using the same test as SimplePageBean.isimage // * html has to return an html type even based on extension, matching test in ShowPageProducer public String getMimeType(String url) { Session s = SessionManager.getCurrentSession(); if (s == null || s.getUserId() == null) { // return ""; } if (SimplePageBean.getYoutubeKeyFromUrl(url) != null) return "application/youtube"; String mimeType = ""; URLConnection conn = null; try { conn = new URL(new URL(ServerConfigurationService.getServerUrl()), url).openConnection(); conn.setConnectTimeout(10000); conn.setReadTimeout(10000); conn.connect(); String t = conn.getContentType(); if (t != null && !t.equals("")) { int i = t.indexOf(";"); if (i >= 0) t = t.substring(0, i); t = t.trim(); mimeType = t; } } catch (Exception e) { } finally { if (conn != null) { try { conn.getInputStream().close(); } catch (Exception e) { // log.error("getTypeOfUrl unable to close " + e); } } } if (mimeType == null || mimeType.equals("")) { String name = url; // starts after last / int i = name.lastIndexOf("/"); if (i >= 0) name = name.substring(i + 1); String extension = null; i = name.lastIndexOf("."); if (i > 0) extension = name.substring(i + 1); if (extension == null) return ""; if (SimplePageBean.imageTypes.contains(extension)) { return "image/unknown"; } if (extension.equals("html") || extension.equals("htm")) { return "text/html"; } else if (extension.equals("xhtml") || extension.equals("xht")) { return "application/xhtml+xml"; } else { return ""; } } return mimeType; } // run antisamy or whatever on a string // WARNING: keep in sync with submit in SimplePageBean. public static String filterHtml(String contents) { StringBuilder error = new StringBuilder(); final Integer FILTER_DEFAULT = 0; final Integer FILTER_HIGH = 1; final Integer FILTER_LOW = 2; final Integer FILTER_NONE = 3; // Sakai currently defaults to high. Unfortunately many // embeds won't work with that. Might want to default this // to low, or add another config parameter, but currently // using same code as for text blocks. String html = contents; // figure out how to filter Integer filter = FILTER_DEFAULT; // simplepagebean checks filterHtml property of tool. We can't really do that. String filterSpec = filterHtml; System.out.println("filterspec " + filterSpec); if (filterSpec == null) // should never be null. unspeciifed should give "" filter = FILTER_DEFAULT; // old specifications else if (filterSpec.equalsIgnoreCase("true")) filter = FILTER_HIGH; // old value of true produced the same result as missing else if (filterSpec.equalsIgnoreCase("false")) filter = FILTER_NONE; // new ones else if (filterSpec.equalsIgnoreCase("default")) filter = FILTER_DEFAULT; else if (filterSpec.equalsIgnoreCase("high")) filter = FILTER_HIGH; else if (filterSpec.equalsIgnoreCase("low")) filter = FILTER_LOW; else if (filterSpec.equalsIgnoreCase("none")) filter = FILTER_NONE; // unspecified else filter = FILTER_DEFAULT; if (filter.equals(FILTER_NONE)) { html = FormattedText.processHtmlDocument(contents, error); } else if (filter.equals(FILTER_DEFAULT)) { html = FormattedText.processFormattedText(contents, error); } else if (ftInstance != null) { try { // now filter is set. Implement it. Depends upon whether we have the anti-samy code Object level = null; if (filter.equals(FILTER_HIGH)) level = levels[1]; else level = levels[2]; html = (String) ftMethod.invoke(ftInstance, new Object[] { contents, error, level }); } catch (Exception e) { // this should never happen. If it does, emulate what the anti-samy // code does if antisamy is disabled. It always filters html = FormattedText.processFormattedText(contents, error); } } else { // don't have antisamy. For LOW, use old instructor behavior, since // LOW is the default. For high, it makes sense to filter if (filter.equals(FILTER_HIGH)) html = FormattedText.processFormattedText(contents, error); else html = FormattedText.processHtmlDocument(contents, error); } return html; } // argument is comma separated list, locale, site, group, group ... public static String groupErrors(String siteId, String locale, String groupString) { locale = locale.trim(); if (locale.length() == 0) locale = null; if (siteId == null) siteId = ""; siteId = siteId.trim(); // currently this is only needed by the instructor String ref = "/site/" + siteId; if (!SecurityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref)) return getMessage("simplepage.nowrite", locale); if (groupString == null) groupString = ""; Map<String, Set<String>> user2groups = new HashMap<String, Set<String>>(); Set<String> overlapGroups = new HashSet<String>(); String[] groupIdArray = groupString.trim().split(","); List<String> groupIds = new ArrayList<String>(); for (String g : groupIdArray) { g = g.trim(); if (g.length() > 0) groupIds.add(g); } Site site = null; Collection<String> users = null; Collection<Group> groups = null; try { // get all users in site and add entries to user@groups // this will have all the groups each user belongs to site = siteService.getSite(siteId); HashSet<String> siteGroup = new HashSet<String>(); siteGroup.add("/site/" + siteId); // not in 2.8 users = authzGroupService.getAuthzUsersInGroups(siteGroup); users = authzGroupService.getUsersIsAllowed("site.visit", siteGroup); for (String userId : users) { user2groups.put(userId, null); } // get list of groups, either specified list or all groups in site if (groupIds.size() > 0) { groups = new HashSet<Group>(); for (String groupId : groupIds) { groups.add(site.getGroup(groupId)); } } else groups = site.getGroups(); // for each group // for each user in group // if user already in a different group, there's an overlap // otherwise remember this user is in this group for (Group group : groups) { users = group.getUsers(); for (String groupUser : users) { Set<String> userGroups = user2groups.get(groupUser); if (userGroups != null) { userGroups.add(group.getId()); overlapGroups.addAll(userGroups); } else { userGroups = new HashSet<String>(); userGroups.add(group.getId()); user2groups.put(groupUser, userGroups); } } } // now output warnings String retmessage = null; if (overlapGroups.size() > 0) { String overlaps = ""; for (String groupId : overlapGroups) { Group group = site.getGroup(groupId); overlaps += ", " + group.getTitle(); } retmessage = getMessage("simplepage.groupcheckoverlaps", locale).replace("{}", overlaps.substring(2)); } String missing = ""; for (Map.Entry<String, Set<String>> entry : user2groups.entrySet()) { if (entry.getValue() == null) { String eid = UserDirectoryService.getUserEid(entry.getKey()); missing += ", " + eid; } } if (missing.length() > 1) { if (retmessage == null) retmessage = ""; else retmessage += "\n\n"; retmessage += getMessage("simplepage.groupchecknogroups", locale).replace("{}", missing.substring(2)); } if (retmessage != null) return "\n\n" + retmessage + "\n\n" + getMessage("simplepage.insist", locale); else return "ok"; } catch (Exception e) { return getMessage("simplepage.groupcheckfailed", locale).replace("{}", e.toString()); } } public static String insertBreakBefore(String itemId, String type, String cols, String csrfToken) { if (itemId == null) { log.error("Ajax insertBreakBefore passed null itemid"); return null; } if (!"section".equals(type) && !"column".equals(type)) { log.error("Ajax insertBreakBefore passed illegal type " + type); return null; } itemId = itemId.trim(); // currently this is only needed by the instructor SimplePageItem item = null; SimplePage page = null; String siteId = null; try { item = simplePageToolDao.findItem(Long.parseLong(itemId)); page = simplePageToolDao.getPage(item.getPageId()); siteId = page.getSiteId(); } catch (Exception e) { e.printStackTrace(); log.error("Ajax insertBreakBefore passed invalid data " + e); return null; } if (siteId == null) { log.error("Ajax insertBreakBefore passed null site id"); return null; } String ref = "/site/" + siteId; if (!SecurityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref) || !checkCsrf(csrfToken)) { log.error("Ajax insertBreakBefore passed itemid " + itemId + " but user doesn't have permission"); return null; } List<SimplePageItem> items = simplePageToolDao.findItemsOnPage(item.getPageId()); // we have an item id. insert before it int nseq = 0; // sequence number of new item boolean after = false; // we found the item to insert before // have an item number specified, look for the item to insert before long before = item.getId(); for (SimplePageItem i : items) { if (i.getId() == before) { // found item to insert before // use its sequence and bump up it and all after nseq = i.getSequence(); after = true; } if (after) { i.setSequence(i.getSequence() + 1); simplePageToolDao.quickUpdate(i); } } // if after not set, we didn't find the item; either no item specified or it if (!after) { log.error("Ajax insertBreakBefore passed item not on its page " + before); return null; } SimplePageItem i = simplePageToolDao.makeItem(item.getPageId(), nseq, SimplePageItem.BREAK, null, null); i.setFormat(type); simplePageToolDao.quickSaveItem(i); return "" + i.getId(); } public static String setColumnProperties(String itemId, String width, String split, String color, String csrfToken) { if (itemId == null || width == null || split == null) { log.error("Ajax setColumnProperties passed null argument"); return null; } itemId = itemId.trim(); // we don't actually use the integers. Just for syntax checking int widthi = 0; int spliti = 0; try { widthi = Integer.parseInt(width); spliti = Integer.parseInt(split); } catch (Exception e) { log.error("Ajax setColumnProperties passwd non-numeric width or split"); return null; } if (color != null) { if (color.equals("")) color = null; else if (!color.matches("^[a-z]*$")) { log.error("Ajax setColumnProperties passwd unreasonable color"); return null; } } // currently this is only needed by the instructor SimplePageItem item = null; SimplePage page = null; String siteId = null; try { Long itemNum = Long.parseLong(itemId); item = simplePageToolDao.findItem(itemNum); if (item.getType() != SimplePageItem.BREAK) { // hopefully this is the first item, in an old page that doesnbt' begin with a page break List<SimplePageItem> items = simplePageToolDao.findItemsOnPage(item.getPageId()); if (items.get(0).getId() == itemNum) { // this is first item on page, add a section break before it item = simplePageToolDao.makeItem(item.getPageId(), 1, SimplePageItem.BREAK, null, null); item.setFormat("section"); simplePageToolDao.quickSaveItem(item); int seq = 2; // and bump sequence numbers for (SimplePageItem i : items) { i.setSequence(seq); simplePageToolDao.quickUpdate(i); seq++; } } else if (items.get(0).getType() == SimplePageItem.BREAK && items.get(1).getId() == itemNum) { // maybe we just inserted a break before our item. // If so, use the break; item = items.get(0); } else { log.error("Ajax setcolumnproperties passed item not a break: " + itemId); } } page = simplePageToolDao.getPage(item.getPageId()); siteId = page.getSiteId(); } catch (Exception e) { e.printStackTrace(); log.error("Ajax setcolumnproperties passed invalid data " + e); return null; } if (siteId == null) { log.error("Ajax setcolumnproperties passed null site id"); return null; } String ref = "/site/" + siteId; if (!SecurityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref) || !checkCsrf(csrfToken)) { log.error("Ajax setcolumnproperties passed itemid " + itemId + " but user doesn't have permission"); return null; } if (width.trim().equals("1")) item.removeAttribute("colwidth"); else item.setAttribute("colwidth", width); if (split.trim().equals("1")) item.removeAttribute("colsplit"); else item.setAttribute("colsplit", split); if (color == null) item.removeAttribute("colcolor"); else item.setAttribute("colcolor", color); simplePageToolDao.quickUpdate(item); return "ok"; } public static String deleteItem(String itemId, String csrfToken) { if (itemId == null) { log.error("Ajax deleteBreak passed null itemid"); return null; } itemId = itemId.trim(); // currently this is only needed by the instructor SimplePageItem item = null; SimplePage page = null; String siteId = null; try { item = simplePageToolDao.findItem(Long.parseLong(itemId)); page = simplePageToolDao.getPage(item.getPageId()); siteId = page.getSiteId(); } catch (Exception e) { e.printStackTrace(); log.error("Ajax deleteBreak passed invalid data " + e); return null; } if (siteId == null) { log.error("Ajax deleteBreak passed null site id"); return null; } String ref = "/site/" + siteId; if (!SecurityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref) || !checkCsrf(csrfToken)) { log.error("Ajax deleteBreak passed itemid " + itemId + " but user doesn't have permission"); return null; } List<SimplePageItem> items = simplePageToolDao.findItemsOnPage(item.getPageId()); // we have an item id. adjust sequence for items after it boolean after = false; // we found the item to delete // have an item number specified, look for it long before = item.getId(); for (SimplePageItem i : items) { if (item.getId() == before) { after = true; } else if (after) { item.setSequence(item.getSequence() - 1); simplePageToolDao.quickUpdate(item); } } simplePageToolDao.quickDelete(item); return "ok"; } public static boolean checkCsrf(String csrfToken) { Object sessionToken = SessionManager.getCurrentSession().getAttribute("sakai.csrf.token"); if (sessionToken != null && sessionToken.toString().equals(csrfToken)) { return true; } else return false; } @SuppressWarnings("unchecked") protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); } @SuppressWarnings("unchecked") protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // get the Tool Placement placement = ToolManager.getCurrentPlacement(); Properties config = null; String placementId = "none"; if (placement != null) { config = placement.getConfig(); placementId = placement.getId(); } res.setContentType("text/html; charset=utf-8"); PrintWriter out = res.getWriter(); String op = req.getParameter("op"); if (op.equals("getmessage")) { String code = req.getParameter("code"); String locale = req.getParameter("locale"); out.println(getMessage(code, locale)); } else if (op.equals("getmimetype")) { String url = req.getParameter("url"); out.println(getMimeType(url)); } else if (op.equals("filterhtml")) { String html = req.getParameter("html"); out.print(filterHtml(html)); } else if (op.equals("getgrouperrors")) { String siteid = req.getParameter("site"); String locale = req.getParameter("locale"); String groups = req.getParameter("groups"); out.print(groupErrors(siteid, locale, groups)); } else if (op.equals("insertbreakbefore")) { String itemId = req.getParameter("itemid"); String type = req.getParameter("type"); String cols = req.getParameter("cols"); String csrfToken = req.getParameter("csrf"); out.println(insertBreakBefore(itemId, type, cols, csrfToken)); } else if (op.equals("setcolumnproperties")) { String itemId = req.getParameter("itemid"); String width = req.getParameter("width"); String split = req.getParameter("split"); String color = req.getParameter("color"); String csrfToken = req.getParameter("csrf"); out.println(setColumnProperties(itemId, width, split, color, csrfToken)); } else if (op.equals("deleteitem")) { String itemId = req.getParameter("itemid"); String csrfToken = req.getParameter("csrf"); out.println(deleteItem(itemId, csrfToken)); } } public void setMessageSource(MessageSource s) { messageSource = s; } public void setSiteService(SiteService s) { siteService = s; } public void setAuthzGroupService(AuthzGroupService s) { authzGroupService = s; } }