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.Scanner;
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 as 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 GroupIterator 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 boolean closed;
047    private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
048    
049    /**
050     * Creates a new group iterator
051     *
052     * @param camelContext  the camel context
053     * @param it            the iterator to group
054     * @param token         then token used to separate between the parts, use <tt>null</tt> to not add the token
055     * @param group         number of parts to group together
056     * @throws IllegalArgumentException is thrown if group is not a positive number
057     * @deprecated  Please use GroupIterator(Exchange exchange, Iterator<?> it, String token, int group) instead
058     */
059    @Deprecated 
060    public GroupIterator(CamelContext camelContext, Iterator<?> it, String token, int group) {
061        this.exchange = null;
062        this.camelContext = camelContext;
063        this.it = it;
064        this.token = token;
065        this.group = group;
066        if (group <= 0) {
067            throw new IllegalArgumentException("Group must be a positive number, was: " + group);
068        }
069    }
070
071    /**
072     * Creates a new group iterator
073     *
074     * @param exchange  the exchange used to create this group iterator
075     * @param it            the iterator to group
076     * @param token         then token used to separate between the parts, use <tt>null</tt> to not add the token
077     * @param group         number of parts to group together
078     * @throws IllegalArgumentException is thrown if group is not a positive number
079     */
080    public GroupIterator(Exchange exchange, Iterator<?> it, String token, int group) {
081        this.exchange = exchange;
082        this.camelContext = exchange.getContext();
083        this.it = it;
084        this.token = token;
085        this.group = group;
086        if (group <= 0) {
087            throw new IllegalArgumentException("Group must be a positive number, was: " + group);
088        }
089    }
090
091    @Override
092    public void close() throws IOException {
093        try {
094            if (it instanceof Scanner) {
095                // special for Scanner which implement the Closeable since JDK7 
096                Scanner scanner = (Scanner) it;
097                scanner.close();
098                IOException ioException = scanner.ioException();
099                if (ioException != null) {
100                    throw ioException;
101                }
102            } else if (it instanceof Closeable) {
103                IOHelper.closeWithException((Closeable) it);
104            }
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            // include token in between
147            if (data != null && count > 0 && token != null) {
148                bos.write(token.getBytes());
149            }
150            if (data instanceof InputStream) {
151                InputStream is = (InputStream) data;
152                IOHelper.copy(is, bos);
153            } else if (data instanceof byte[]) {
154                byte[] bytes = (byte[]) data;
155                bos.write(bytes);
156            } else if (data != null) {
157                // convert to input stream
158                InputStream is = camelContext.getTypeConverter().mandatoryConvertTo(InputStream.class, data);
159                IOHelper.copy(is, bos);
160            }
161
162            count++;
163        }
164
165        // prepare and return answer as String using exchange's charset
166        String answer = bos.toString(IOHelper.getCharsetName(exchange));
167        bos.reset();
168        return answer;
169    }
170
171    @Override
172    public void remove() {
173        it.remove();
174    }
175}