/** * Returns list of folder id string if the query is a simple appointments query. Otherwise returns * null. * * @param params search parameters * @param zsc not used, may be used in subclass * @throws ServiceException subclass may throw */ protected List<String> getFolderIdListIfSimpleAppointmentsQuery( SearchParams params, ZimbraSoapContext zsc) throws ServiceException { // types = "appointment" Set<MailItem.Type> types = params.getTypes(); if (types.size() != 1) { return null; } MailItem.Type type = Iterables.getOnlyElement(types); if (type != MailItem.Type.APPOINTMENT && type != MailItem.Type.TASK) { return null; } // has time range if (params.getCalItemExpandStart() == -1 || params.getCalItemExpandEnd() == -1) { return null; } // offset = 0 if (params.getOffset() != 0) { return null; } // sortBy = "none" SortBy sortBy = params.getSortBy(); if (sortBy != null && !sortBy.equals(SortBy.NONE)) { return null; } // query string is "inid:<folder> [OR inid:<folder>]*" String queryString = Strings.nullToEmpty(params.getQueryString()); queryString = queryString.toLowerCase(); queryString = removeOuterParens(queryString); // simple appointment query can't have any ANDed terms if (queryString.contains("and")) { return null; } String[] terms = queryString.split("\\s+or\\s+"); List<String> folderIdStrs = new ArrayList<String>(); for (String term : terms) { term = term.trim(); // remove outermost parentheses (light client does this, e.g. "(inid:10)") term = removeOuterParens(term); if (!term.startsWith("inid:")) { return null; } String folderId = term.substring(5); // everything after "inid:" folderId = unquote(folderId); // e.g. if query is: inid:"account-id:num", we want just account-id:num if (folderId.length() > 0) { folderIdStrs.add(folderId); } } return folderIdStrs; }
private static void searchRemoteAccountCalendars( Element parent, SearchParams params, ZimbraSoapContext zsc, Account authAcct, Map<String, List<Integer>> accountFolders) throws ServiceException { String nominalTargetAcctId = null; // mail service soap requests want to see a target account StringBuilder queryStr = new StringBuilder(); for (Map.Entry<String, List<Integer>> entry : accountFolders.entrySet()) { String acctId = entry.getKey(); if (nominalTargetAcctId == null) nominalTargetAcctId = acctId; ItemIdFormatter ifmt = new ItemIdFormatter(authAcct.getId(), acctId, false); List<Integer> folderIds = entry.getValue(); for (int folderId : folderIds) { if (queryStr.length() > 0) queryStr.append(" OR "); // must quote the qualified folder id queryStr.append("inid:\"").append(ifmt.formatItemId(folderId)).append("\""); } } Element req = zsc.createElement(MailConstants.SEARCH_REQUEST); req.addAttribute(MailConstants.A_SEARCH_TYPES, MailItem.Type.toString(params.getTypes())); if (params.getSortBy() != null) { req.addAttribute(MailConstants.A_SORTBY, params.getSortBy().toString()); } req.addAttribute(MailConstants.A_QUERY_OFFSET, params.getOffset()); if (params.getLimit() != 0) req.addAttribute(MailConstants.A_QUERY_LIMIT, params.getLimit()); req.addAttribute(MailConstants.A_CAL_EXPAND_INST_START, params.getCalItemExpandStart()); req.addAttribute(MailConstants.A_CAL_EXPAND_INST_END, params.getCalItemExpandEnd()); req.addAttribute(MailConstants.E_QUERY, queryStr.toString(), Element.Disposition.CONTENT); Account target = Provisioning.getInstance().get(Key.AccountBy.id, nominalTargetAcctId); String pxyAuthToken = zsc.getAuthToken().getProxyAuthToken(); ZAuthToken zat = pxyAuthToken == null ? zsc.getRawAuthToken() : new ZAuthToken(pxyAuthToken); ZMailbox.Options zoptions = new ZMailbox.Options(zat, AccountUtil.getSoapUri(target)); zoptions.setTargetAccount(nominalTargetAcctId); zoptions.setTargetAccountBy(AccountBy.id); zoptions.setNoSession(true); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); Element resp = zmbx.invoke(req); for (Element hit : resp.listElements()) { hit.detach(); parent.addElement(hit); } }
@Override public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Mailbox mbox = getRequestedMailbox(zsc); Account account = getRequestedAccount(zsc); OperationContext octxt = getOperationContext(zsc, context); SearchRequest req = JaxbUtil.elementToJaxb(request); if (Objects.firstNonNull(req.getWarmup(), false)) { mbox.index.getIndexStore().warmup(); return zsc.createElement(MailConstants.SEARCH_RESPONSE); } SearchParams params = SearchParams.parse(req, zsc, account.getPrefMailInitialSearch()); if (params.getLocale() == null) { params.setLocale(mbox.getAccount().getLocale()); } if (params.inDumpster() && params.getTypes().contains(MailItem.Type.CONVERSATION)) { throw ServiceException.INVALID_REQUEST("cannot search for conversations in dumpster", null); } if (LC.calendar_cache_enabled.booleanValue()) { List<String> apptFolderIds = getFolderIdListIfSimpleAppointmentsQuery(params, zsc); if (apptFolderIds != null) { Account authAcct = getAuthenticatedAccount(zsc); Element response = zsc.createElement(MailConstants.SEARCH_RESPONSE); runSimpleAppointmentQuery(response, params, octxt, zsc, authAcct, mbox, apptFolderIds); return response; } } ZimbraQueryResults results = mbox.index.search(zsc.getResponseProtocol(), octxt, params); try { // create the XML response Element Element response = zsc.createElement(MailConstants.SEARCH_RESPONSE); // must use results.getSortBy() because the results might have ignored our sortBy // request and used something else... response.addAttribute(MailConstants.A_SORTBY, results.getSortBy().toString()); putHits(zsc, octxt, response, results, params); return response; } finally { Closeables.closeQuietly(results); } }
private static void runSimpleAppointmentQuery( Element parent, SearchParams params, OperationContext octxt, ZimbraSoapContext zsc, Account authAcct, Mailbox mbox, List<String> folderIdStrs) throws ServiceException { Set<MailItem.Type> types = params.getTypes(); MailItem.Type type = types.size() == 1 ? Iterables.getOnlyElement(types) : MailItem.Type.APPOINTMENT; if (params.getSortBy() != null) { parent.addAttribute(MailConstants.A_SORTBY, params.getSortBy().toString()); } parent.addAttribute(MailConstants.A_QUERY_OFFSET, params.getOffset()); parent.addAttribute(MailConstants.A_QUERY_MORE, false); List<ItemId> folderIids = new ArrayList<ItemId>(folderIdStrs.size()); for (String folderIdStr : folderIdStrs) { folderIids.add(new ItemId(folderIdStr, zsc)); } Provisioning prov = Provisioning.getInstance(); MailboxManager mboxMgr = MailboxManager.getInstance(); Server localServer = prov.getLocalServer(); Map<Server, Map<String /* account id */, List<Integer> /* folder ids */>> groupedByServer = groupByServer(ItemId.groupFoldersByAccount(octxt, mbox, folderIids)); // Look up in calendar cache first. if (LC.calendar_cache_enabled.booleanValue()) { CalSummaryCache calCache = CalendarCacheManager.getInstance().getSummaryCache(); long rangeStart = params.getCalItemExpandStart(); long rangeEnd = params.getCalItemExpandEnd(); for (Iterator<Map.Entry<Server, Map<String, List<Integer>>>> serverIter = groupedByServer.entrySet().iterator(); serverIter.hasNext(); ) { Map.Entry<Server, Map<String, List<Integer>>> serverMapEntry = serverIter.next(); Map<String, List<Integer>> accountFolders = serverMapEntry.getValue(); // for each account for (Iterator<Map.Entry<String, List<Integer>>> acctIter = accountFolders.entrySet().iterator(); acctIter.hasNext(); ) { Map.Entry<String, List<Integer>> acctEntry = acctIter.next(); String acctId = acctEntry.getKey(); List<Integer> folderIds = acctEntry.getValue(); ItemIdFormatter ifmt = new ItemIdFormatter(authAcct.getId(), acctId, false); // for each folder for (Iterator<Integer> iterFolderId = folderIds.iterator(); iterFolderId.hasNext(); ) { int folderId = iterFolderId.next(); try { CalendarDataResult result = calCache.getCalendarSummary( octxt, acctId, folderId, type, rangeStart, rangeEnd, true); if (result != null) { // Found data in cache. iterFolderId.remove(); addCalendarDataToResponse( parent, zsc, ifmt, result.data, result.allowPrivateAccess); } } catch (ServiceException e) { String ecode = e.getCode(); if (ecode.equals(ServiceException.PERM_DENIED)) { // share permission was revoked ZimbraLog.calendar.warn( "Ignoring permission error during calendar search of folder " + ifmt.formatItemId(folderId), e); } else if (ecode.equals(MailServiceException.NO_SUCH_FOLDER)) { // shared calendar folder was deleted by the owner ZimbraLog.calendar.warn( "Ignoring deleted calendar folder " + ifmt.formatItemId(folderId)); } else { throw e; } iterFolderId.remove(); } } if (folderIds.isEmpty()) acctIter.remove(); } if (accountFolders.isEmpty()) serverIter.remove(); } } // For any remaining calendars, we have to get the data the hard way. for (Map.Entry<Server, Map<String, List<Integer>>> serverMapEntry : groupedByServer.entrySet()) { Server server = serverMapEntry.getKey(); Map<String, List<Integer>> accountFolders = serverMapEntry.getValue(); if (server.equals(localServer)) { // local server for (Map.Entry<String, List<Integer>> entry : accountFolders.entrySet()) { String acctId = entry.getKey(); List<Integer> folderIds = entry.getValue(); if (folderIds.isEmpty()) continue; Account targetAcct = prov.get(AccountBy.id, acctId); if (targetAcct == null) { ZimbraLog.calendar.warn( "Skipping unknown account " + acctId + " during calendar search"); continue; } Mailbox targetMbox = mboxMgr.getMailboxByAccount(targetAcct); searchLocalAccountCalendars( parent, params, octxt, zsc, authAcct, targetMbox, folderIds, type); } } else { // remote server searchRemoteAccountCalendars(parent, params, zsc, authAcct, accountFolders); } } }