/** * */ package webctdbexport.utils; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URLEncoder; import java.sql.Blob; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.hibernate.Query; import org.hibernate.Session; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import webctdbexport.db.CmsContentEntry; import webctdbexport.db.CmsFileContent; import webctdbexport.db.CmsLink; import webctdbexport.db.CoOrganizerlink; import webctdbexport.db.CoUrl; import webctdbexport.db.LearningContext; import webctdbexport.db.Person; /** Helper methods for implementing Moodle 2 Repository API over WebCT-DB. * * @author cmg * */ public class MoodleRepository { static Logger logger = Logger.getLogger(MoodleRepository.class.getName()); public static final String PATH = "path"; public static final String TITLE = "title"; public static final String SIZE = "size"; //public static final String URL = "url"; public static final String SOURCE = "source"; public static final String NAME = "name"; public static final String LIST = "list"; private static final String PERSON_TYPE = "p"; private static final String LC_TYPE = "lc"; private static final String FC_TYPE = "fc"; private static final String CE_TYPE = "ce"; private static final String CHILDREN = "children"; public static final String DESCRIPTION = "description"; public static final String WEBCT_TYPE = "webcttype"; private static JSONObject getListingObject() throws JSONException { JSONObject obj = new JSONObject(); obj.put("dynload", true); obj.put("nologin", true); obj.put("nosearch", true); return obj; } private static JSONObject getFileObject(String title, String description, String webctType, long lastModified, long size, String url) throws JSONException { JSONObject fileobj = new JSONObject(); fileobj.put(TITLE, title); if (description!=null) fileobj.put(DESCRIPTION, description); if (webctType!=null) fileobj.put(WEBCT_TYPE, webctType); // if (lastModified!=0) // fileobj.put(DATE, format?); fileobj.put(SIZE, size); // fileobj.put(URL, url); fileobj.put(SOURCE, url); return fileobj; } private static JSONObject getLinkObject(String title, String description, String webctType, long lastModified, long size, String url) throws JSONException { JSONObject fileobj = new JSONObject(); fileobj.put(TITLE, title); if (description!=null) fileobj.put(DESCRIPTION, description); if (webctType!=null) fileobj.put(WEBCT_TYPE, webctType); // if (lastModified!=0) // fileobj.put(DATE, format?); fileobj.put(SIZE, size); fileobj.put(SOURCE, url); return fileobj; } private static JSONObject getFolderObject(String title, String description, String webctType, String path, long lastModified) throws JSONException { JSONObject fileobj = new JSONObject(); fileobj.put(TITLE, title); if (description!=null) fileobj.put(DESCRIPTION, description); if (webctType!=null) fileobj.put(WEBCT_TYPE, webctType); // if (lastModified!=0) // fileobj.put(DATE, format?); fileobj.put(SIZE, 0); fileobj.put(PATH, path); fileobj.put(CHILDREN, new JSONArray()); return fileobj; } /** get_listing response for a user, i.e. modules and person * @param showLinks * @param showFiles * @throws JSONException */ public static JSONObject getListingForUser(Session s, String username, boolean showFiles, boolean showLinks) throws JSONException { JSONObject userobj = getListingObject(); JSONArray path = new JSONArray(); userobj.put(PATH, path); String root = "/"; path.put(getPathObject("WebCT", root)); JSONArray list = new JSONArray(); userobj.put(LIST, list); // person for username? Person p = DbUtils.getPersonByWebctId(s, username); if (p==null) { logger.log(Level.WARNING, "Could not find Person "+username); return userobj; } if (showFiles) { list.put(getFolderObject(username, null, "Person folder", root+getFilename(p)+"/", 0)); } // LCs List<LearningContext> lcs = DbUtils.getLearningContextsForPersonAsRole(s, p, DbUtils.getRoleDefinitionForSectionDesigner(s)); Collections.sort(lcs, new LearningContextComparator()); for (LearningContext lc : lcs) { list.put(getFolderObject(lc.getName(), DbUtils.getDescription(lc), lc.getLcType().getTypeCode(), root+getFilename(lc)+"/", 0)); } return userobj; } /** get_listing response for a whole DB, i.e. institutions. * @param showLinks * @param showFiles * @throws JSONException */ public static JSONObject getListingForRoot(Session s) throws JSONException { JSONObject userobj = getListingObject(); JSONArray path = new JSONArray(); userobj.put(PATH, path); String root = "/"; path.put(getPathObject("WebCT", root)); JSONArray list = new JSONArray(); userobj.put(LIST, list); List institutions = DbUtils.getLearningContextsOfType(s, DbUtils.getLcType(s, DbUtils.LC_INSTITUTION)); for (Object lcio : institutions) { LearningContext lc = (LearningContext)lcio; logger.log(Level.INFO, "Institution: "+lc.getId()+" "+lc.getName()); list.put(getFolderObject(lc.getName(), DbUtils.getDescription(lc), lc.getLcType().getTypeCode(), root+getFilename(lc)+"/", 0)); } return userobj; } private static JSONObject getPathObject(String name, String path) throws JSONException { JSONObject pathobj = new JSONObject(); pathobj.put(NAME, name); pathobj.put(PATH, path); return pathobj; } private static String getFilename(Person p) { return PERSON_TYPE+p.getId(); } private static String getFilename(LearningContext lc) { return LC_TYPE+lc.getId(); } private static String getFilename(CmsFileContent fc) { return FC_TYPE+fc.getId(); } /** get_listing for a path, i.e. person, learning context, content entry, ... * @param showLinks * @param showFiles * @throws JSONException * @throws UnsupportedEncodingException */ public static JSONObject getListingForPath(Session s, String path, boolean showFiles, boolean showLinks) throws JSONException, UnsupportedEncodingException { JSONObject listing = getListingObject(); JSONArray patharr = new JSONArray(); listing.put(PATH, patharr); String root = "/"; patharr.put(getPathObject("WebCT", root)); String elements [] = path.split("/"); StringBuffer pathbuf = new StringBuffer(); pathbuf.append("/"); for (int ei=0; ei<elements.length; ei++) { String filename = elements[ei]; if (filename.length()==0) continue; logger.log(Level.INFO, "Path element "+ei+": "+filename); Object pathel = getPathElementObject(s, filename); String name = getPathElementName(pathel); pathbuf.append(filename); pathbuf.append("/"); JSONObject pathobj = new JSONObject(); pathobj.put(NAME, name!=null ? name : "null"); pathobj.put(PATH, pathbuf.toString()); patharr.put(pathobj); } if (elements.length==0) { logger.log(Level.WARNING, "path is empty ("+path+")"); return listing; } JSONArray list = new JSONArray(); String filename = elements[elements.length-1]; Object pathel = getPathElementObject(s, filename); CmsContentEntry ce = getPathElementContentEntry(s, pathel); if (ce!=null) list = getChildren(s, ce, path, showFiles, showLinks); if (pathel instanceof LearningContext) { LearningContext lc = (LearningContext)pathel; Set<LearningContext> children = lc.getLearningContexts(); List<LearningContext> lcs = new LinkedList<LearningContext>(); for (LearningContext child : children) { lcs.add(child); } Collections.sort(lcs, new LearningContextComparator()); for (LearningContext child : lcs) { String childTypeCode = child.getLcType().getTypeCode(); list.put(getFolderObject(child.getName(), DbUtils.getDescription(child), childTypeCode, path+getFilename(child)+"/", 0)); } } listing.put(LIST, list); return listing; } private static Object getPathElementObject(Session s, String filename) { String type = getFilenameType(filename); BigDecimal id = getFilenameId(filename); if (PERSON_TYPE.equals(type)) { Person p = (Person)s.createQuery("from Person as p where p.id=?").setBigDecimal(0, id).uniqueResult(); if (p==null) { logger.log(Level.WARNING, "Could not find Person "+id); return null; } return p; } else if (LC_TYPE.equals(type)) { LearningContext lc = (LearningContext)s.createQuery("from LearningContext as lc where lc.id=?").setBigDecimal(0, id).uniqueResult(); if (lc==null) { logger.log(Level.WARNING, "Could not file LearningContext "+id); return null; } return lc; } else if (CE_TYPE.equals(type)) { CmsContentEntry ce = (CmsContentEntry)s.createQuery("from CmsContentEntry as ce where ce.id=?").setBigDecimal(0, id).uniqueResult(); if (ce==null) { logger.log(Level.WARNING, "Could not file CmsContentEntry "+id); return null; } return ce; } else if (FC_TYPE.equals(type)) { CmsFileContent fc = (CmsFileContent)s.createQuery("from CmsFileContent as fc where fc.id=?").setBigDecimal(0, id).uniqueResult(); if (fc==null) { logger.log(Level.WARNING, "Could not file CmsFileContent "+id); return null; } return fc; } logger.log(Level.WARNING, "Unknown path element type "+filename); return null; } private static CmsContentEntry getPathElementContentEntry(Session s, Object pathElementObject) { if (pathElementObject==null) return null; if (pathElementObject instanceof Person) { Person p = (Person)pathElementObject; BigDecimal folderId = p.getHomefolderId(); CmsContentEntry ce = (CmsContentEntry)s.createQuery("from CmsContentEntry as ce where ce.id=?").setBigDecimal(0, folderId).uniqueResult(); return ce; } else if (pathElementObject instanceof LearningContext) { LearningContext lc = (LearningContext)pathElementObject; String lcTypeCode = lc.getLcType().getTypeCode(); // top-level if (DbUtils.LC_INSTITUTION.equals(lcTypeCode)) return null; // next level - year, school, etc. if (DbUtils.LC_COURSE.equals(lcTypeCode)) return null; //|| DbUtils.LC_ CmsContentEntry ce = lc.getCmsContentEntry(); if (ce==null) { logger.log(Level.WARNING, "LearningContext "+lc.getId()+" had no CmsContentEntry"); return null; } logger.log(Level.INFO, "LearningContext CmsContentEntry is type "+DbUtils.getTypename(ce)); return ce; } else if (pathElementObject instanceof CmsContentEntry) { CmsContentEntry ce = (CmsContentEntry)pathElementObject; return ce; } else { logger.log(Level.WARNING, "pathElementObject of unsupported type "+pathElementObject.getClass()); return null; } } private static String getPathElementName(Object pathElementObject) { if (pathElementObject instanceof Person) { Person p = (Person)pathElementObject; return p.getWebctId(); } else if (pathElementObject instanceof LearningContext) { LearningContext lc = (LearningContext)pathElementObject; return lc.getName(); } else if (pathElementObject instanceof CmsContentEntry) { CmsContentEntry ce = (CmsContentEntry)pathElementObject; return ce.getName(); } else { if (pathElementObject!=null) logger.log(Level.WARNING, "pathElementName of unsupported type "+pathElementObject.getClass()); return null; } } private static JSONArray sortChildren(JSONArray list) throws JSONException { Vector<JSONObject> items = new Vector<JSONObject>(); for (int li=0; li<list.length(); li++) items.add(list.getJSONObject(li)); Collections.sort(items, new JSONObjectTitleComparator()); JSONArray children = new JSONArray(); for (JSONObject o : items) children.put(o); return children; } private static JSONArray getChildren(Session s, CmsContentEntry ce, String path, boolean showFiles, boolean showLinks) throws JSONException, UnsupportedEncodingException { JSONArray list = new JSONArray(); String typename = DbUtils.getTypename(ce); // other special cases... // - learning context -> Container/LcHomeFolder -> Container/RepositoryFolder -> Template/Default [files] // -> ORGANIZER_PAGE_TYPE/Default -> ContentFile/... | PAGE_TYPE/Default | URL_TYPE/Default // | WebLinkContainer/Default -> WEBLINKSCATEGORY/Default -> URL_TYPE/Default // | Folder/Default // TODO if (DbUtils.LC_HOME_FOLDER_TYPE.equals(typename)) { Set<CmsContentEntry> children = ce.getCmsContentEntriesForParentId(); if (children.size()==1) { ce = children.iterator().next(); typename = DbUtils.getTypename(ce); } if (DbUtils.REPOSITORY_FOLDER_TYPE.equals(typename)) { children = ce.getCmsContentEntriesForParentId(); if (children.size()==1) { CmsContentEntry filesce = children.iterator().next(); String filesTypename = DbUtils.getTypename(filesce); if (DbUtils.TEMPLATE_TYPE.equals(filesTypename)) { if (showFiles) list.put(getItem(s, filesce, "Files", null, path)); // just what we expected // look for ORGANIZER_PAGE_TYPE/Default and WebLinkContainer/Default { //ce.getCmsContentEntryByParentId() //ce.getCmsCeSubtype().getCmsCeType().getName() Query q = s.createQuery("from CmsContentEntry as ce where ce.cmsContentEntryByParentId=? and ce.cmsCeSubtype.cmsCeType.name='ORGANIZER_PAGE_TYPE'").setEntity(0, filesce); List ces = q.list(); for (Object ceo : ces) { CmsContentEntry orgce = (CmsContentEntry)ceo; list.put(getItem(s, orgce, "Home Page", orgce.getName(), path)); } } if (showLinks) { Query q = s.createQuery("from CmsContentEntry as ce where ce.cmsContentEntryByParentId=? and ce.cmsCeSubtype.cmsCeType.name='WebLinkContainer'").setEntity(0, filesce); List ces = q.list(); for (Object ceo : ces) { CmsContentEntry orgce = (CmsContentEntry)ceo; list.put(getItem(s, orgce, "Links", orgce.getName(), path)); } } return list; } } } } if (DbUtils.ORGANIZER_PAGE_TYPE.equals(typename)) { Set<CmsLink> linkset = ce.getCmsLinksForLeftobjectId(); List<CmsLink> links = new ArrayList<CmsLink>(); links.addAll(linkset); Collections.sort(links, new OrganizerlinkComparator()); for(CmsLink link : links) { CmsContentEntry child = link.getCmsContentEntryByRightobjectId(); // default name/desc from linked item String linkName = child.getName(); String linkDesc = DbUtils.getText(child.getDescription()); CoOrganizerlink orgLink = link.getCoOrganizerlink(); if (orgLink!=null) { // should be! - override?! if (orgLink.getLinkname()!=null) linkName = orgLink.getLinkname(); if (orgLink.getLongDescription()!=null) linkDesc = DbUtils.getText(orgLink.getLongDescription()); } if (include(child, showFiles, showLinks)) list.put(getItem(s, child, linkName, linkDesc, path)); } } else if (DbUtils.TEMPLATE_TYPE.equals(typename)) { // filter for files only Set<CmsContentEntry> children = ce.getCmsContentEntriesForParentId(); for (CmsContentEntry child : children) { String childTypename = DbUtils.getTypename(child); if (DbUtils.FOLDER_TYPE.equals(childTypename) || child.getCmsFileContent()!=null) { if (include(child, showFiles, showLinks)) list.put(getItem(s, child, path)); } else logger.fine("Skipping item type "+childTypename+" under "+DbUtils.TEMPLATE_TYPE); } list = sortChildren(list); } else { // children... Set<CmsContentEntry> children = ce.getCmsContentEntriesForParentId(); for (CmsContentEntry child : children) { if (include(child, showFiles, showLinks)) list.put(getItem(s, child, path)); } list = sortChildren(list); } return list; } private static boolean include(CmsContentEntry ce, boolean showFiles, boolean showLinks) { if (ce.getCmsFileContent()!=null) { return showFiles; } String typename = DbUtils.getTypename(ce); if (DbUtils.PAGE_TYPE.equals(typename)) { // Page Type = link Set<CmsLink> links = ce.getCmsLinksForLeftobjectId(); if (links.size()>0) { if (links.size()>1) { logger.log(Level.WARNING, "PAGE_TYPE with "+links.size()+" links! - only using first"); } CmsLink link = links.iterator().next(); CmsContentEntry child = link.getCmsContentEntryByRightobjectId(); if (child!=null) return include(child, showFiles, showLinks); logger.log(Level.WARNING,"PAGE_TYPE with link without child"); } } if (DbUtils.URL_TYPE.equals(typename)) { return showLinks; } // yes? return true; } /** get file/folder JSONObject for this entry * @throws JSONException * @throws UnsupportedEncodingException */ private static JSONObject getItem(Session s, CmsContentEntry ce, String parentPath) throws JSONException, UnsupportedEncodingException { String name = ce.getName(); String description = DbUtils.getDescription(ce); return getItem(s, ce, name, description, parentPath); } private static JSONObject getItem(Session s, CmsContentEntry ce, String name, String description, String parentPath) throws JSONException, UnsupportedEncodingException { String typename = DbUtils.getTypename(ce); CoUrl url = ce.getCoUrl(); if (url!=null && url.getLink()!=null) { return getLinkObject(name, description, typename, 0, 0, url.getLink()); } CmsFileContent fc = ce.getCmsFileContent(); if (fc!=null) { String filename = getFilename(fc); // fcNNN/actual-file-name?? long len = 0; try { Blob blob = fc.getContent(); len = blob.length(); //blob.free(); } catch (Exception e) { logger.log(Level.WARNING, "Could not get length of FileContent "+fc.getId(), e); } return getFileObject(name, description, typename, 0, len, parentPath+filename+"/"+URLEncoder.encode(ce.getName(), "UTF-8")); } else { if (DbUtils.PAGE_TYPE.equals(typename)) { // Page Type = link Set<CmsLink> links = ce.getCmsLinksForLeftobjectId(); if (links.size()>0) { if (links.size()>1) { logger.log(Level.WARNING, "PAGE_TYPE with "+links.size()+" links! - only using first"); } CmsLink link = links.iterator().next(); CmsContentEntry child = link.getCmsContentEntryByRightobjectId(); if (child!=null) return getItem(s, child, name, description, parentPath); logger.log(Level.WARNING,"PAGE_TYPE with link without child"); } return getFileObject(name, description, typename, 0, 0, parentPath+getFilename(ce)+".error"); } else { // Organizer // other folder type String filename = getFilename(ce); String path = parentPath+filename+"/"; JSONObject folderobj = getFolderObject(name, description, typename, path, 0); //folderobj.put(CHILDREN, getChildren(s, ce, path)); return folderobj; } } } private static String getFilename(CmsContentEntry ce) { return CE_TYPE+ce.getId(); } private static BigDecimal getFilenameId(String filename) { int i=0; while(i<filename.length() && !Character.isDigit(filename.charAt(i))) i++; return new BigDecimal(filename.substring(i)); } private static String getFilenameType(String filename) { int i=0; while(i<filename.length() && !Character.isDigit(filename.charAt(i))) i++; return filename.substring(0, i); } /** get_file for 'url' returned from getListingForPath * @throws SQLException */ public static File getFile(Session s, String url, File tmpdir) throws IOException, SQLException { CmsFileContent fc = getFileContent(s, url); if (fc==null) return null; File file = new File(tmpdir, url); file.getParentFile().mkdirs(); long len = 0; logger.info("Download "+file); DbUtils.dumpCmsFileContent(fc, file); return file; } public static CmsFileContent getFileContent(Session s, String url) throws IOException { if (url.startsWith("http")) // external URL... return null; int ix= url.lastIndexOf("/"); if (ix<0 || ix>=url.length()-1) { throw new IOException("getFile for invalid file url "+url); } String pathElements[] = url.split("/"); String filename = pathElements[pathElements.length-2]; Object pathobj = getPathElementObject(s, filename); if (!(pathobj instanceof CmsFileContent)) throw new IOException("getFile for non-CmsFileContent "+url); CmsFileContent fc = (CmsFileContent)pathobj; return fc; } }