/* * Copyright (c) 2010 Lockheed Martin Corporation * * Licensed 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.eurekastreams.server.action.execution.stream; import java.util.ArrayList; import java.util.List; import net.sf.json.JSONObject; import org.apache.commons.logging.Log; import org.eurekastreams.commons.logging.LogFactory; import org.eurekastreams.server.persistence.mappers.DomainMapper; import org.eurekastreams.server.search.modelview.PersonModelView; import org.eurekastreams.server.service.actions.strategies.activity.ListCollider; import org.eurekastreams.server.service.actions.strategies.activity.datasources.DescendingOrderDataSource; import org.eurekastreams.server.service.actions.strategies.activity.datasources.SortedDataSource; /** * Get Activity IDs with a JSON request. */ public class GetActivityIdsByJson { /** * Logger. */ private Log log = LogFactory.make(); /** * Data source that MUST provide results in descending order of ID. */ private DescendingOrderDataSource descendingOrderdataSource; /** * Data source that MUST provide results in the order they will be given to the user. */ private SortedDataSource sortedDataSource; /** * AND collider. */ private ListCollider andCollider; /** * Security trimmer. */ private ActivitySecurityTrimmer securityTrimmer; /** * Person mapper. */ private DomainMapper<List<Long>, List<PersonModelView>> personMapper; /** * String to replace with user name. */ private String userReplaceString; /** * Public constructor for AOP. */ public GetActivityIdsByJson() { } /** * Default constructor. * * @param inDescendingOrderdataSource * the data sources to fetch the sorted descending data from. * @param inSortedDataSource * the data sources to fetch the sorted data from. * @param inAndCollider * the and collider to merge results. * @param inSecurityTrimmer * the security trimmer; * @param inPersonMapper * the person mapper. * @param inUserRelaceString * the string to replace with the user id. */ public GetActivityIdsByJson(final DescendingOrderDataSource inDescendingOrderdataSource, final SortedDataSource inSortedDataSource, final ListCollider inAndCollider, final ActivitySecurityTrimmer inSecurityTrimmer, final DomainMapper<List<Long>, List<PersonModelView>> inPersonMapper, final String inUserRelaceString) { descendingOrderdataSource = inDescendingOrderdataSource; sortedDataSource = inSortedDataSource; andCollider = inAndCollider; securityTrimmer = inSecurityTrimmer; personMapper = inPersonMapper; userReplaceString = inUserRelaceString; } /** * Get activity ids base on a request and user entity ID. * * @param inRequest * the request. * @param userEntityId * the user entity ID. * @return the activity ids. */ public List<Long> execute(final String inRequest, final Long userEntityId) { String request = inRequest; log.debug("Attempted to parse: " + inRequest); if (request.contains(userReplaceString)) { List<Long> peopleIds = new ArrayList<Long>(); peopleIds.add(userEntityId); request = request.replaceAll(userReplaceString, personMapper.execute(peopleIds).get(0).getAccountId()); } final JSONObject jsonRequest = JSONObject.fromObject(request); int maxResults = 0; long minActivityId = 0; long maxActivityId = Long.MAX_VALUE; if (jsonRequest.containsKey("count")) { maxResults = jsonRequest.getInt("count"); } if (jsonRequest.containsKey("minId")) { minActivityId = jsonRequest.getInt("minId"); } if (jsonRequest.containsKey("maxId")) { maxActivityId = jsonRequest.getLong("maxId"); } // used for paging, this is the next activity in the list to add to the // current page int startingIndex = 0; // the list of activities to return List<Long> results = new ArrayList<Long>(); List<Long> allKeys = new ArrayList<Long>(); final List<Long> sortedDataSet = sortedDataSource.fetch(jsonRequest, userEntityId); // The pass. int pass = 1; int batchSize = 0; // build a list of activity ids to fetch for this page, and // increment the start index for next page List<Long> page = new ArrayList<Long>(); do { allKeys.clear(); // multiply the batch size by the multiplier to avoid extra cache hits batchSize = maxResults * (int) (Math.pow(2, pass)); jsonRequest.put("count", batchSize); final List<Long> descendingOrderDataSet = descendingOrderdataSource.fetch(jsonRequest, userEntityId); if (descendingOrderDataSet != null && sortedDataSet != null) { // we have both lists allKeys = andCollider.collide(descendingOrderDataSet, sortedDataSet, batchSize); } else if (descendingOrderDataSet != null) { allKeys = descendingOrderDataSet; } else if (sortedDataSet != null) { allKeys = sortedDataSet; } if (allKeys.size() < startingIndex) { // no more log.debug("No more results to page through after " + allKeys.size()); break; } // loop across the available keys in allKeys for (int i = startingIndex; i < allKeys.size(); i++, startingIndex++) { // if this is within our limits, include it for security trimming if (allKeys.get(i) < maxActivityId && allKeys.get(i) > minActivityId) { page.add(allKeys.get(i)); if (page.size() == batchSize || i == allKeys.size() - 1) { log.debug("Sending a page of " + page.size() + " out for security trimming."); // we've filled up a page - either by hitting our batch size or by hitting the end of allKeys, // so security trim it page = securityTrimmer.trim(page, userEntityId); // add the trimmed results to our return list for (Long item : page) { results.add(item); if (results.size() >= maxResults) { log.debug("Filled a full page of " + results.size() + " results."); return results; } } log.info("Return results now has " + results.size() + " results - looking for more"); // start over for our next scoped page page = new ArrayList<Long>(); } } } // we've looped across allPages and haven't yet found enough results - increment the pass, and try // getting more into allKeys pass++; log.trace("Done looping?: " + (allKeys.size() < batchSize) + ", pass: " + pass + ", results.size(): " + results.size() + ", maxResults: " + maxResults + ", allKeys.size(): " + allKeys.size() + ", batchSize: " + batchSize); } while (allKeys.size() >= batchSize); // while we got back at least as many as we needed for the recent batch if (results.size() < maxResults && page.size() > 0) { // we haven't gotten all our results yet, and we still have results to security trim page = securityTrimmer.trim(page, userEntityId); for (Long item : page) { results.add(item); if (results.size() >= maxResults) { log.debug("Filled a full page of " + results.size() + " results."); return results; } } } return results; } }