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; 025import java.util.concurrent.atomic.AtomicBoolean; 026 027import org.apache.camel.CamelContext; 028import org.apache.camel.Exchange; 029import org.apache.camel.NoTypeConversionAvailableException; 030 031/** 032 * Group based {@link Iterator} which groups the given {@link Iterator} a number of times 033 * and then return a combined response as a String. 034 * <p/> 035 * This implementation uses an internal byte array buffer, to combine the response. 036 * The token is inserted between the individual parts. 037 * <p/> 038 * For example if you group by new line, then a new line token is inserted between the lines. 039 */ 040public final class GroupTokenIterator implements Iterator<Object>, Closeable { 041 042 private final CamelContext camelContext; 043 private final Exchange exchange; 044 private final Iterator<?> it; 045 private final String token; 046 private final int group; 047 private final boolean skipFirst; 048 private final AtomicBoolean hasSkipFirst; 049 private boolean closed; 050 private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 051 052 /** 053 * Creates a new token based group iterator 054 * 055 * @param camelContext the camel context 056 * @param it the iterator to group 057 * @param token then token used to separate between the parts, use <tt>null</tt> to not add the token 058 * @param group number of parts to group together 059 * @throws IllegalArgumentException is thrown if group is not a positive number 060 * @deprecated Please use GroupIterator(Exchange exchange, Iterator<?> it, String token, int group) instead 061 */ 062 @Deprecated 063 public GroupTokenIterator(CamelContext camelContext, Iterator<?> it, String token, int group) { 064 this.exchange = null; 065 this.camelContext = camelContext; 066 this.it = it; 067 this.token = token; 068 this.group = group; 069 if (group <= 0) { 070 throw new IllegalArgumentException("Group must be a positive number, was: " + group); 071 } 072 this.skipFirst = false; 073 this.hasSkipFirst = null; 074 } 075 076 /** 077 * Creates a new token based group iterator 078 * 079 * @param exchange the exchange used to create this group iterator 080 * @param it the iterator to group 081 * @param token then token used to separate between the parts, use <tt>null</tt> to not add the token 082 * @param group number of parts to group together 083 * @throws IllegalArgumentException is thrown if group is not a positive number 084 */ 085 public GroupTokenIterator(Exchange exchange, Iterator<?> it, String token, int group, boolean skipFirst) { 086 this.exchange = exchange; 087 this.camelContext = exchange.getContext(); 088 this.it = it; 089 this.token = token; 090 this.group = group; 091 if (group <= 0) { 092 throw new IllegalArgumentException("Group must be a positive number, was: " + group); 093 } 094 this.skipFirst = skipFirst; 095 if (skipFirst) { 096 this.hasSkipFirst = new AtomicBoolean(); 097 } else { 098 this.hasSkipFirst = null; 099 } 100 } 101 102 @Override 103 public void close() throws IOException { 104 try { 105 if (it instanceof Scanner) { 106 // special for Scanner which implement the Closeable since JDK7 107 Scanner scanner = (Scanner) it; 108 scanner.close(); 109 IOException ioException = scanner.ioException(); 110 if (ioException != null) { 111 throw ioException; 112 } 113 } else if (it instanceof Closeable) { 114 IOHelper.closeWithException((Closeable) it); 115 } 116 } finally { 117 // close the buffer as well 118 bos.close(); 119 // we are now closed 120 closed = true; 121 } 122 } 123 124 @Override 125 public boolean hasNext() { 126 if (closed) { 127 return false; 128 } 129 130 boolean answer = it.hasNext(); 131 if (!answer) { 132 // auto close 133 try { 134 close(); 135 } catch (IOException e) { 136 // ignore 137 } 138 } 139 return answer; 140 } 141 142 @Override 143 public Object next() { 144 try { 145 return doNext(); 146 } catch (Exception e) { 147 throw ObjectHelper.wrapRuntimeCamelException(e); 148 } 149 } 150 151 private Object doNext() throws IOException, NoTypeConversionAvailableException { 152 int count = 0; 153 Object data = ""; 154 while (count < group && it.hasNext()) { 155 data = it.next(); 156 157 if (skipFirst && hasSkipFirst.compareAndSet(false, true)) { 158 if (it.hasNext()) { 159 data = it.next(); 160 } else { 161 // Content with header only which is marked to skip 162 data = ""; 163 } 164 } 165 166 // include token in between 167 if (data != null && count > 0 && token != null) { 168 bos.write(token.getBytes()); 169 } 170 if (data instanceof InputStream) { 171 InputStream is = (InputStream) data; 172 IOHelper.copy(is, bos); 173 } else if (data instanceof byte[]) { 174 byte[] bytes = (byte[]) data; 175 bos.write(bytes); 176 } else if (data != null) { 177 // convert to input stream 178 InputStream is = camelContext.getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, data); 179 IOHelper.copy(is, bos); 180 } 181 182 count++; 183 } 184 185 // prepare and return answer as String using exchange's charset 186 String answer = bos.toString(IOHelper.getCharsetName(exchange)); 187 bos.reset(); 188 return answer; 189 } 190 191 @Override 192 public void remove() { 193 it.remove(); 194 } 195}