001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.io.ByteArrayOutputStream;
020import java.io.Closeable;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Iterator;
024import java.util.concurrent.atomic.AtomicBoolean;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.Exchange;
028import org.apache.camel.NoTypeConversionAvailableException;
029
030/**
031 * Group based {@link Iterator} which groups the given {@link Iterator} a number of times
032 * and then return a combined response as a String.
033 * <p/>
034 * This implementation uses an internal byte array buffer, to combine the response.
035 * The token is inserted between the individual parts.
036 * <p/>
037 * For example if you group by new line, then a new line token is inserted between the lines.
038 */
039public final class GroupTokenIterator implements Iterator<Object>, Closeable {
040
041    private final CamelContext camelContext;
042    private final Exchange exchange;
043    private final Iterator<?> it;
044    private final String token;
045    private final int group;
046    private final boolean skipFirst;
047    private final AtomicBoolean hasSkipFirst;
048    private boolean closed;
049    private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
050    
051    /**
052     * Creates a new token based group iterator
053     *
054     * @param camelContext  the camel context
055     * @param it            the iterator to group
056     * @param token         then token used to separate between the parts, use <tt>null</tt> to not add the token
057     * @param group         number of parts to group together
058     * @throws IllegalArgumentException is thrown if group is not a positive number
059     * @deprecated  Please use GroupIterator(Exchange exchange, Iterator<?> it, String token, int group) instead
060     */
061    @Deprecated 
062    public GroupTokenIterator(CamelContext camelContext, Iterator<?> it, String token, int group) {
063        this.exchange = null;
064        this.camelContext = camelContext;
065        this.it = it;
066        this.token = token;
067        this.group = group;
068        if (group <= 0) {
069            throw new IllegalArgumentException("Group must be a positive number, was: " + group);
070        }
071        this.skipFirst = false;
072        this.hasSkipFirst = null;
073    }
074
075    /**
076     * Creates a new token based group iterator
077     *
078     * @param exchange      the exchange used to create this group iterator
079     * @param it            the iterator to group
080     * @param token         then token used to separate between the parts, use <tt>null</tt> to not add the token
081     * @param group         number of parts to group together
082     * @throws IllegalArgumentException is thrown if group is not a positive number
083     */
084    public GroupTokenIterator(Exchange exchange, Iterator<?> it, String token, int group, boolean skipFirst) {
085        this.exchange = exchange;
086        this.camelContext = exchange.getContext();
087        this.it = it;
088        this.token = token;
089        this.group = group;
090        if (group <= 0) {
091            throw new IllegalArgumentException("Group must be a positive number, was: " + group);
092        }
093        this.skipFirst = skipFirst;
094        if (skipFirst) {
095            this.hasSkipFirst = new AtomicBoolean();
096        } else {
097            this.hasSkipFirst = null;
098        }
099    }
100
101    @Override
102    public void close() throws IOException {
103        try {
104            IOHelper.closeIterator(it);
105        } finally {
106            // close the buffer as well
107            bos.close();
108            // we are now closed
109            closed = true;
110        }
111    }
112
113    @Override
114    public boolean hasNext() {
115        if (closed) {
116            return false;
117        }
118
119        boolean answer = it.hasNext();
120        if (!answer) {
121            // auto close
122            try {
123                close();
124            } catch (IOException e) {
125                // ignore
126            }
127        }
128        return answer;
129    }
130
131    @Override
132    public Object next() {
133        try {
134            return doNext();
135        } catch (Exception e) {
136            throw ObjectHelper.wrapRuntimeCamelException(e);
137        }
138    }
139
140    private Object doNext() throws IOException, NoTypeConversionAvailableException {
141        int count = 0;
142        Object data = "";
143        while (count < group && it.hasNext()) {
144            data = it.next();
145
146            if (skipFirst && hasSkipFirst.compareAndSet(false, true)) {
147                if (it.hasNext()) {
148                    data = it.next();
149                } else {
150                    // Content with header only which is marked to skip
151                    data = "";
152                }
153            }
154
155            // include token in between
156            if (data != null && count > 0 && token != null) {
157                bos.write(token.getBytes());
158            }
159            if (data instanceof InputStream) {
160                InputStream is = (InputStream) data;
161                IOHelper.copy(is, bos);
162            } else if (data instanceof byte[]) {
163                byte[] bytes = (byte[]) data;
164                bos.write(bytes);
165            } else if (data != null) {
166                // convert to input stream
167                InputStream is = camelContext.getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, data);
168                IOHelper.copy(is, bos);
169            }
170
171            count++;
172        }
173
174        // prepare and return answer as String using exchange's charset
175        String answer = bos.toString(IOHelper.getCharsetName(exchange));
176        bos.reset();
177        return answer;
178    }
179
180    @Override
181    public void remove() {
182        it.remove();
183    }
184}