001/* 002 * Copyright 2015-2018 Ping Identity Corporation 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim2.server; 019 020import com.fasterxml.jackson.core.JsonGenerator; 021import com.fasterxml.jackson.databind.JsonNode; 022import com.fasterxml.jackson.databind.node.ObjectNode; 023import com.unboundid.scim2.common.ScimResource; 024import com.unboundid.scim2.common.utils.JsonUtils; 025 026import java.io.IOException; 027import java.io.OutputStream; 028import java.util.Iterator; 029import java.util.Map; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.concurrent.atomic.AtomicInteger; 032 033/** 034 * An interface for writing list/query results using the SCIM ListResponse 035 * container to an OutputStream. 036 */ 037public class ListResponseWriter<T extends ScimResource> 038{ 039 private final JsonGenerator jsonGenerator; 040 041 private final AtomicBoolean startedResourcesArray = new AtomicBoolean(); 042 private final AtomicBoolean sentTotalResults = new AtomicBoolean(); 043 private final AtomicInteger resultsSent = new AtomicInteger(); 044 045 private ObjectNode deferredFields; 046 047 /** 048 * Create a new ListResponseOutputStream that will write to the provided 049 * output stream. 050 * 051 * @param outputStream The output stream to write to. 052 * @throws IOException If an exception occurs while writing to the output 053 * stream. 054 */ 055 public ListResponseWriter(final OutputStream outputStream) 056 throws IOException 057 { 058 jsonGenerator = 059 JsonUtils.getObjectReader().getFactory().createGenerator(outputStream); 060 deferredFields = JsonUtils.getJsonNodeFactory().objectNode(); 061 } 062 063 /** 064 * Start the response. 065 * 066 * @throws IOException If an exception occurs while writing to the output 067 * stream. 068 */ 069 void startResponse() throws IOException 070 { 071 jsonGenerator.writeStartObject(); 072 jsonGenerator.writeArrayFieldStart("schemas"); 073 jsonGenerator.writeString( 074 "urn:ietf:params:scim:api:messages:2.0:ListResponse"); 075 jsonGenerator.writeEndArray(); 076 } 077 078 /** 079 * End the response. 080 * 081 * @throws IOException If an exception occurs while writing to the output 082 * stream. 083 */ 084 void endResponse() throws IOException 085 { 086 if(!sentTotalResults.get() && !deferredFields.has("totalResults")) 087 { 088 // The total results was never set. Set it to the calculated one. 089 totalResults(resultsSent.get()); 090 } 091 if(startedResourcesArray.get()) 092 { 093 // Close the resources array if currently writing it. 094 jsonGenerator.writeEndArray(); 095 } 096 097 Iterator<Map.Entry<String, JsonNode>> i = deferredFields.fields(); 098 while(i.hasNext()) 099 { 100 Map.Entry<String, JsonNode> field = i.next(); 101 jsonGenerator.writeObjectField(field.getKey(), field.getValue()); 102 } 103 jsonGenerator.writeEndObject(); 104 jsonGenerator.flush(); 105 jsonGenerator.close(); 106 } 107 108 /** 109 * Write the startIndex to the output stream immediately if no resources have 110 * been streamed, otherwise it will be written after the resources array. 111 * 112 * @param startIndex The startIndex to write. 113 * @throws IOException If an exception occurs while writing to the output 114 * stream. 115 */ 116 public void startIndex(final int startIndex) throws IOException 117 { 118 if(startedResourcesArray.get()) 119 { 120 deferredFields.put("startIndex", startIndex); 121 } 122 else 123 { 124 jsonGenerator.writeNumberField("startIndex", startIndex); 125 } 126 } 127 128 /** 129 * Write the itemsPerPage to the output stream immediately if no resources 130 * have been streamed, otherwise it will be written after the resources array. 131 * 132 * @param itemsPerPage The itemsPerPage to write. 133 * @throws IOException If an exception occurs while writing to the output 134 * stream. 135 */ 136 public void itemsPerPage(final int itemsPerPage) throws IOException 137 { 138 if(startedResourcesArray.get()) 139 { 140 deferredFields.put("itemsPerPage", itemsPerPage); 141 } 142 else 143 { 144 jsonGenerator.writeNumberField("itemsPerPage", itemsPerPage); 145 } 146 } 147 148 /** 149 * Write the totalResults to the output stream immediately if no resources 150 * have been streamed, otherwise it will be written after the resources array. 151 * 152 * @param totalResults The totalResults to write. 153 * @throws IOException If an exception occurs while writing to the output 154 * stream. 155 */ 156 public void totalResults(final int totalResults) throws IOException 157 { 158 if(startedResourcesArray.get()) 159 { 160 deferredFields.put("totalResults", totalResults); 161 } 162 else 163 { 164 jsonGenerator.writeNumberField("totalResults", totalResults); 165 sentTotalResults.set(true); 166 } 167 } 168 169 /** 170 * Write the result resource to the output stream immediately. 171 * 172 * @param scimResource The resource to write. 173 * @throws IOException If an exception occurs while writing to the output 174 * stream. 175 */ 176 public void resource(final T scimResource) throws IOException 177 { 178 if(startedResourcesArray.compareAndSet(false, true)) 179 { 180 jsonGenerator.writeArrayFieldStart("Resources"); 181 } 182 jsonGenerator.writeObject(scimResource); 183 resultsSent.incrementAndGet(); 184 } 185}