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}