Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== RCS file: /usr/local/cvsroot/lams_central/conf/language/lams/ApplicationResources.properties,v diff -u -r1.63 -r1.64 --- lams_central/conf/language/lams/ApplicationResources.properties 28 Feb 2009 13:46:32 -0000 1.63 +++ lams_central/conf/language/lams/ApplicationResources.properties 3 Mar 2009 15:49:24 -0000 1.64 @@ -299,7 +299,10 @@ label.planner.grouping.view.students=View students before selection error.planner.filter.parse=There was an error when parsing the filter query. -label.planner.filter.output=These nodes are filtered. -label.planner.filter.clear=View nodes without filtering. -label.planner.filter=Filter +label.planner.filter.clear=Clear search results +label.planner.filter=Search +label.planner.filter.results.beginning=Search results for " +label.planner.filter.results.ending=" in +label.planner.locked=(locked) +label.planner.filter.find.location=Found in: #======= End labels: Exported 222 labels for en AU ===== Index: lams_central/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== RCS file: /usr/local/cvsroot/lams_central/conf/language/lams/ApplicationResources_en_AU.properties,v diff -u -r1.60 -r1.61 --- lams_central/conf/language/lams/ApplicationResources_en_AU.properties 28 Feb 2009 13:46:32 -0000 1.60 +++ lams_central/conf/language/lams/ApplicationResources_en_AU.properties 3 Mar 2009 15:49:24 -0000 1.61 @@ -299,7 +299,10 @@ label.planner.grouping.view.students=View students before selection error.planner.filter.parse=There was an error when parsing the filter query. -label.planner.filter.output=These nodes are filtered. -label.planner.filter.clear=View nodes without filtering. -label.planner.filter=Filter +label.planner.filter.clear=Clear search results +label.planner.filter=Search +label.planner.filter.results.beginning=Search results for " +label.planner.filter.results.ending=" in +label.planner.locked=(locked) +label.planner.filter.find.location=Found in: #======= End labels: Exported 222 labels for en AU ===== Index: lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java =================================================================== RCS file: /usr/local/cvsroot/lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java,v diff -u -r1.14 -r1.15 --- lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java 1 Mar 2009 10:10:11 -0000 1.14 +++ lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java 3 Mar 2009 15:49:24 -0000 1.15 @@ -35,7 +35,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import javax.servlet.ServletException; @@ -47,6 +49,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.analysis.StopAnalyzer; import org.apache.lucene.analysis.snowball.SnowballAnalyzer; import org.apache.lucene.document.Document; @@ -210,9 +213,30 @@ private static final String FIELD_NAME_BRIEF_DESCRIPTION = "briefDescription"; private static final String FIELD_NAME_FULL_DESCRIPTION = "fullDescription"; private static final String FIELD_NAME_ANCESTOR_UID = "ancestorUid"; - // not sfinal as it may be later swapped by different language - private static Analyzer ANALYZER = new SnowballAnalyzer("English", StopAnalyzer.ENGLISH_STOP_WORDS); + private static final Map filterLanguageMap = new TreeMap(); + private static final Map filterStopWordsMap = new TreeMap(); + + static { + PedagogicalPlannerAction.filterLanguageMap.put("en", "English"); + PedagogicalPlannerAction.filterLanguageMap.put("nl", "Dutch"); + PedagogicalPlannerAction.filterLanguageMap.put("da", "Danish"); + PedagogicalPlannerAction.filterLanguageMap.put("nl", "Finnish"); + PedagogicalPlannerAction.filterLanguageMap.put("fr", "French"); + PedagogicalPlannerAction.filterLanguageMap.put("de", "German"); + PedagogicalPlannerAction.filterLanguageMap.put("hu", "Hungarian"); + PedagogicalPlannerAction.filterLanguageMap.put("it", "Italian"); + PedagogicalPlannerAction.filterLanguageMap.put("no", "Norwegian"); + PedagogicalPlannerAction.filterLanguageMap.put("pt", "Portuguese"); + PedagogicalPlannerAction.filterLanguageMap.put("ru", "Russian"); + PedagogicalPlannerAction.filterLanguageMap.put("es", "Spanish"); + PedagogicalPlannerAction.filterLanguageMap.put("sv", "Swedish"); + PedagogicalPlannerAction.filterLanguageMap.put("tr", "Turkish"); + + PedagogicalPlannerAction.filterStopWordsMap.put("English", StopAnalyzer.ENGLISH_STOP_WORDS); + // ^[\s\|]+(\S+).*$ + } + @Override /** * Go straight to open sequence node. @@ -559,6 +583,7 @@ Boolean isSysAdmin = request.isUserInRole(Role.SYSADMIN); Boolean edit = WebUtil.readBooleanParam(request, CentralConstants.PARAM_EDIT, false); edit &= isSysAdmin; + String filterText = WebUtil.readStrParam(request, CentralConstants.PARAM_FILTER_TEXT, true); // Do we display the root (top) node or an existing one PedagogicalPlannerSequenceNode node = null; if (nodeUid == null) { @@ -573,31 +598,51 @@ PedagogicalPlannerAction.log.debug("Opening sequence node with UID: " + nodeUid); // Fill the DTO - List titlePath = getPedagogicalPlannerDAO().getTitlePath(node); - String filterText = WebUtil.readStrParam(request, CentralConstants.PARAM_FILTER_TEXT, true); - Set filteredNodeUids = null; - try { - filteredNodeUids = filterSubnodes(node, filterText); - } catch (Exception e) { - PedagogicalPlannerAction.log.error(e, e); - ActionMessages errors = new ActionMessages(); - errors.add(ActionMessages.GLOBAL_MESSAGE, - new ActionMessage(PedagogicalPlannerAction.ERROR_KEY_FILTER_PARSE)); - saveErrors(request, errors); + PedagogicalPlannerSequenceNodeDTO dto = null; + if (filterText != null) { + try { + // Filtering = display node and all the subnodes that were found in the search (not the immediate + // children of the node) + Set filteredNodeUids = filterSubnodes(node, filterText); + if (filteredNodeUids != null) { + request.setAttribute(CentralConstants.PARAM_FILTER_TEXT, filterText); + + Set filteredNodes = new HashSet( + filteredNodeUids.size()); + for (Long filteredUid : filteredNodeUids) { + PedagogicalPlannerSequenceNode subnode = getPedagogicalPlannerDAO().getByUid(filteredUid); + filteredNodes.add(subnode); + } + + dto = new PedagogicalPlannerSequenceNodeDTO(node, filteredNodes); + for (PedagogicalPlannerSequenceNodeDTO subnodeDTO : dto.getSubnodes()) { + List titlePath = getPedagogicalPlannerDAO().getTitlePath(subnodeDTO.getUid()); + subnodeDTO.setTitlePath(titlePath); + } + } + } catch (Exception e) { + PedagogicalPlannerAction.log.error(e, e); + ActionMessages errors = new ActionMessages(); + errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( + PedagogicalPlannerAction.ERROR_KEY_FILTER_PARSE)); + saveErrors(request, errors); + } } - if (filteredNodeUids != null) { - request.setAttribute(CentralConstants.PARAM_FILTER_TEXT, filterText); + + if (dto == null) { + // No filtering or something went wrong in filtering + dto = new PedagogicalPlannerSequenceNodeDTO(node, node.getSubnodes()); } - PedagogicalPlannerSequenceNodeDTO dto = new PedagogicalPlannerSequenceNodeDTO(node, titlePath, filteredNodeUids); - // Additional DTO parameters + List titlePath = getPedagogicalPlannerDAO().getTitlePath(nodeUid); Boolean createSubnode = WebUtil.readBooleanParam(request, CentralConstants.PARAM_CREATE_SUBNODE, false); Boolean importNode = WebUtil.readBooleanParam(request, CentralConstants.PARAM_IMPORT_NODE, false); dto.setCreateSubnode(createSubnode); dto.setEdit(edit); dto.setIsSysAdmin(isSysAdmin); dto.setImportNode(importNode); + dto.setTitlePath(titlePath); request.setAttribute(CentralConstants.ATTR_NODE, dto); @@ -1226,48 +1271,66 @@ inputStream.close(); } + /** + * Finds all node's descendants matching the query. Results can be not only the subnodes of the node, but also + * deeper descendants. This method uses Lucene project for query parsing and searchig. + * + * @param node + * @param filterText + * @return set of nodes' uids + * @throws ParseException + * @throws CorruptIndexException + * @throws IOException + */ private Set filterSubnodes(PedagogicalPlannerSequenceNode node, String filterText) throws ParseException, CorruptIndexException, IOException { + Set matchingSubnodeUids = new TreeSet(); if (!StringUtils.isEmpty(filterText)) { - Set docs = new HashSet(); - extractSubnodeDocuments(docs, node, null); - if (docs.isEmpty()) { - return null; - } + Set docs = extractSubnodeDocuments(node); + if (!docs.isEmpty()) { + Analyzer analyzer = getAnalyzer(); + // Searching is performed in title, brief description and full description of the node. + MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[] { + PedagogicalPlannerAction.FIELD_NAME_TITLE, + PedagogicalPlannerAction.FIELD_NAME_FULL_DESCRIPTION, + PedagogicalPlannerAction.FIELD_NAME_BRIEF_DESCRIPTION }, analyzer); - MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[] { - PedagogicalPlannerAction.FIELD_NAME_TITLE, PedagogicalPlannerAction.FIELD_NAME_FULL_DESCRIPTION, - PedagogicalPlannerAction.FIELD_NAME_BRIEF_DESCRIPTION }, PedagogicalPlannerAction.ANALYZER); + Query query = queryParser.parse(filterText); - Query query = queryParser.parse(filterText); + // Index is store in the operational memory (not on a hard drive) + RAMDirectory dir = new RAMDirectory(); + IndexWriter indexWriter = new IndexWriter(dir, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED); + for (Document doc : docs) { + indexWriter.addDocument(doc); + } + indexWriter.optimize(); + indexWriter.close(); - RAMDirectory dir = new RAMDirectory(); - IndexWriter indexWriter = new IndexWriter(dir, PedagogicalPlannerAction.ANALYZER, true, - IndexWriter.MaxFieldLength.UNLIMITED); - for (Document doc : docs) { - indexWriter.addDocument(doc); - } - indexWriter.optimize(); - indexWriter.close(); + IndexSearcher searcher = new IndexSearcher(dir); - IndexSearcher searcher = new IndexSearcher(dir); - int maxHits = node.getSubnodes().size(); - TopDocs topDocs = searcher.search(query, null, maxHits); + TopDocs topDocs = searcher.search(query, null, docs.size()); - Set matchingSubnodeUids = new TreeSet(); - for (ScoreDoc scoreDoc : topDocs.scoreDocs) { - Document doc = searcher.doc(scoreDoc.doc); - String ancestorUid = doc.get(PedagogicalPlannerAction.FIELD_NAME_ANCESTOR_UID); - Long uid = new Long(ancestorUid); - matchingSubnodeUids.add(uid); + for (ScoreDoc scoreDoc : topDocs.scoreDocs) { + Document doc = searcher.doc(scoreDoc.doc); + String ancestorUid = doc.get(PedagogicalPlannerAction.FIELD_NAME_ANCESTOR_UID); + Long uid = new Long(ancestorUid); + matchingSubnodeUids.add(uid); + } } - return matchingSubnodeUids; } - return null; + return matchingSubnodeUids; } - private void extractSubnodeDocuments(Set docs, PedagogicalPlannerSequenceNode node, String ancestorNodeUid) { + /** + * Adds documents made of subnodes' title, descriptions and uid, then descents deeper into descendants. + * + * @param node + * its subnodes will be parsed + * @return documents made of all of node's descendants + */ + private Set extractSubnodeDocuments(PedagogicalPlannerSequenceNode node) { + Set docs = new HashSet(); if (node != null && node.getSubnodes() != null) { for (PedagogicalPlannerSequenceNode subnode : node.getSubnodes()) { Document doc = new Document(); @@ -1281,24 +1344,40 @@ Field.Store.NO, Field.Index.ANALYZED); doc.add(briefDescField); } - String fullDesc = WebUtil.removeHTMLtags(subnode.getBriefDescription()); + String fullDesc = WebUtil.removeHTMLtags(subnode.getFullDescription()); if (fullDesc != null) { Field fullDescField = new Field(PedagogicalPlannerAction.FIELD_NAME_FULL_DESCRIPTION, fullDesc, Field.Store.NO, Field.Index.ANALYZED); doc.add(fullDescField); } - String uid = ancestorNodeUid == null ? subnode.getUid().toString() : ancestorNodeUid; - Field ancestorUidField = new Field(PedagogicalPlannerAction.FIELD_NAME_ANCESTOR_UID, uid, - Field.Store.YES, Field.Index.NOT_ANALYZED); - doc.add(ancestorUidField); - + Field uidField = new Field(PedagogicalPlannerAction.FIELD_NAME_ANCESTOR_UID, subnode.getUid() + .toString(), Field.Store.YES, Field.Index.NOT_ANALYZED); + doc.add(uidField); docs.add(doc); - extractSubnodeDocuments(docs, subnode, uid); + + Set subnodeDocs = extractSubnodeDocuments(subnode); + docs.addAll(subnodeDocs); } } + return docs; } + private Analyzer getAnalyzer() { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + String languageCode = user == null ? null : user.getLocaleLanguage(); + String language = PedagogicalPlannerAction.filterLanguageMap.get(languageCode); + if (language == null) { + return new SimpleAnalyzer(); + } + String[] stopWords = PedagogicalPlannerAction.filterStopWordsMap.get(language); + if (stopWords == null) { + return new SnowballAnalyzer(language); + } + return new SnowballAnalyzer(language, stopWords); + } + /*----------------------- GROUPING METHODS -------------------------*/ /** Index: lams_central/web/pedagogical_planner/sequenceChooser.jsp =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/pedagogical_planner/Attic/sequenceChooser.jsp,v diff -u -r1.6 -r1.7 --- lams_central/web/pedagogical_planner/sequenceChooser.jsp 28 Feb 2009 13:46:32 -0000 1.6 +++ lams_central/web/pedagogical_planner/sequenceChooser.jsp 3 Mar 2009 15:49:24 -0000 1.7 @@ -12,7 +12,7 @@ <fmt:message key="title.lams"/> :: <fmt:message key="planner.title" /> - + @@ -39,12 +39,12 @@ <%-- This part creates path like "> Root > Some node > Some subnode" with links to upper level nodes --%> - + <%-- Always add root node at the beginning --%> - > + > <%-- Iterate through subnodes, if any --%> @@ -55,16 +55,13 @@ > - - <%-- Add title (but no link) of the current node at the end --%> - + - @@ -78,32 +75,48 @@ + + - - - - - - - - - + + + + + + + + + + + + + <%-- List of subnodes --%> <%-- If the list of subnodes is empty, we display only a message --%> @@ -123,39 +136,41 @@ <%-- Cell with icons (info or actions like remove node) --%> - <%-- If we are in the edit mode, we display remove and move up/down images --%> - - - - - - - - - - - " - onclick="javascript:leaveNodeEditor('','${removeNodeUrl}')" /> - - - + + <%-- If we are in the edit mode (and not in filter mode), we display remove and move up/down images --%> + + - " - onclick="javascript:leaveNodeEditor(null,'${upNodeUrl}')" /> - - - " - onclick="javascript:leaveNodeEditor(null,'${downNodeUrl}')" /> + " + onclick="javascript:leaveNodeEditor('','${removeNodeUrl}')" /> + + + + + + + " + onclick="javascript:leaveNodeEditor(null,'${upNodeUrl}')" /> + + + + + + + + " + onclick="javascript:leaveNodeEditor(null,'${downNodeUrl}')" /> + @@ -181,9 +196,28 @@ - (locked) + - + + + +
+ <%-- If we are in filter mode, we display the path where was the subnode found --%> + + <%-- Always add root node at the beginning --%> + > + <%-- Iterate through subnodes, if any --%> + + + + + + + > + +
+
+
${subnode.briefDescription}
@@ -192,7 +226,7 @@
- <%-- Title and full description of the node --%> - - - -
-
- -

-
${node.fullDescription}
-
-
-
-

- -

-
+ <%-- Title and full description of the node, only if we are not in filtering mode--%> + + +
+
+ +

+
${node.fullDescription}
+
+
+
+ <%-- Filter mode: what were we looking for and in what node --%> +

+ + + + + + + + +

+
- + <%-- Form for editing the node --%>
@@ -356,7 +390,7 @@
- + Index: lams_central/web/pedagogical_planner/templateBase.jsp =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/pedagogical_planner/Attic/templateBase.jsp,v diff -u -r1.3 -r1.4 --- lams_central/web/pedagogical_planner/templateBase.jsp 26 Feb 2009 19:20:09 -0000 1.3 +++ lams_central/web/pedagogical_planner/templateBase.jsp 3 Mar 2009 15:49:24 -0000 1.4 @@ -20,15 +20,15 @@ var saveDetailsUrl = ""; var errorPlannerNotSaved = ''; - - - - - - - - - + + + + + + + +