001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.model.control.graphql.server.util.count; 018 019import com.echothree.util.common.transfer.Limit; 020import com.echothree.util.server.persistence.ThreadSession; 021import graphql.schema.DataFetchingEnvironment; 022import java.util.Map; 023 024public class ObjectLimiter 025 implements AutoCloseable { 026 027 private static final String PARAMETER_FIRST = "first"; 028 private static final String PARAMETER_AFTER = "after"; 029 private static final String PARAMETER_LAST = "last"; 030 private static final String PARAMETER_BEFORE = "before"; 031 032 private final DataFetchingEnvironment env; 033 private final String componentVendorName; 034 private final String entityTypeName; 035 private final long totalCount; 036 037 private Map<String, Limit> limits; 038 private Limit savedLimit; 039 private long limitOffset; 040 private long limitCount; 041 042 public String getComponentVendorName() { 043 return componentVendorName; 044 } 045 046 public String getEntityTypeName() { 047 return entityTypeName; 048 } 049 050 public long getTotalCount() { 051 return totalCount; 052 } 053 054 public long getLimitOffset() { 055 return limitOffset; 056 } 057 058 public long getLimitCount() { 059 return limitCount; 060 } 061 062 public ObjectLimiter(final DataFetchingEnvironment env, final String componentVendorName, final String entityTypeName, 063 final long totalCount) { 064 this.env = env; 065 this.componentVendorName = componentVendorName; 066 this.entityTypeName = entityTypeName; 067 this.totalCount = totalCount; 068 069 var session = ThreadSession.currentSession(); 070 var first = env.<Integer>getArgument(PARAMETER_FIRST); 071 var after = GraphQlCursorUtils.getInstance().fromCursor(componentVendorName, entityTypeName, env.getArgument(PARAMETER_AFTER)); 072 var last = env.<Integer>getArgument(PARAMETER_LAST); 073 var before = GraphQlCursorUtils.getInstance().fromCursor(componentVendorName, entityTypeName, env.getArgument(PARAMETER_BEFORE)); 074 075 // Initialize edges to be allEdges. 076 limitOffset = 0; 077 limitCount = totalCount; 078 079 // Source: https://relay.dev/graphql/connections.htm 080 // 4.4 Pagination algorithm 081 if(first != null || after != null || last != null || before != null) { 082 limits = session.getLimits(); 083 savedLimit = limits.get(entityTypeName); 084 085 // If after is set: && If after exists: 086 if(after != null && after <= totalCount) { 087 // Remove all elements of edges before and including after. 088 limitOffset = after; 089 limitCount -= after; 090 } 091 092 // If before is set: && If before exists: 093 if(before != null && before > 0 && before <= totalCount) { 094 // Remove all elements of edges after and including before. 095 limitCount = before - limitOffset - 1; 096 } 097 098 // TODO: If first is less than 0: Throw an error. (Currently no error is thrown.) 099 100 // If first is set: 101 if(first != null && first > 0) { 102 // If edges has length greater than first: 103 if(limitCount > first) { 104 // Slice edges to be of length first by removing edges from the end of edges. 105 limitCount = first; 106 } 107 } 108 109 // TODO: If last is less than 0: Throw an error. (Currently no error is thrown.) 110 111 // If last is set: 112 if(last != null && last > 0) { 113 // If edges has length greater than last: 114 if(limitCount > last) { 115 // Slice edges to be of length last by removing edges from the start of edges. 116 limitOffset = limitOffset + limitCount - last; 117 limitCount = last; 118 } 119 } 120 121 limits.put(entityTypeName, new Limit(Long.toString(limitCount), Long.toString(limitOffset))); 122 } 123 } 124 125 @Override 126 public void close() { 127 if(limits != null) { 128 // Restore previous Limit; 129 limits.put(entityTypeName, savedLimit); 130 } 131 } 132 133}