diff options
| author | Michael Peter Christen <mc@yacy.net> | 2025-08-26 10:44:06 -0700 |
|---|---|---|
| committer | Michael Peter Christen <mc@yacy.net> | 2025-08-26 10:44:06 -0700 |
| commit | 8451cfdef8f3e40cb866c4ee74fbf21d73325136 (patch) | |
| tree | a112276944c10e180a04ce2c5227ad9dcac9d5d6 | |
| parent | c2060cc15d95a2870af910b489ac3a1d042d2257 (diff) | |
enabling very large file uploads >2gb
This is a complete re-design of the serverObjects data structure which
holds all data that is submitted during http post requests to YaCy.
Before the change, post attributes had been stored to Strings which
cannot be larger than 2GB. Furthermore, byte[] uploads had been encoded
to b64 Strings to fit into this data structure. Those strings are now
replaced by a new data structure, ChunkedBytes which is an object that
can hold more than 2GB data using a list of byte[] objects. All required
streaming functions are implemented and streaming from http post upload
into this data structure works. The b64 encoding has been removed. The
ZIM and WARC reader make use of the new data structure.
35 files changed, 1347 insertions, 470 deletions
diff --git a/source/net/yacy/cora/util/ChunkedBytes.java b/source/net/yacy/cora/util/ChunkedBytes.java new file mode 100644 index 000000000..798ed28e9 --- /dev/null +++ b/source/net/yacy/cora/util/ChunkedBytes.java @@ -0,0 +1,861 @@ +/** + * ChunkedBytes + * Copyright 26.8.2025 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program in the file lgpl21.txt + * If not, see <http://www.gnu.org/licenses/>. + */ + +package net.yacy.cora.util; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +import net.yacy.cora.document.encoding.UTF8; + +/** + * This class implements an output stream in which the data is + * written into an arbitrary-length store which is composed of + * RAM chunks and/or file-mapped chunks. + */ +public final class ChunkedBytes extends OutputStream implements Comparable<Object>, Closeable { + + /** Keep mapped/heap chunks well under Integer.MAX_VALUE; 64 MiB is a good default. */ + public static final int CHUNK_SIZE = 64 * 1024 * 1024; + + private final List<Segment> segments; + private long size; + + public ChunkedBytes() { + this.segments = new ArrayList<>(); + this.size = 0; + } + + public ChunkedBytes(InputStream in) throws IOException { + this(); + this.writeFrom(in); + } + + public ChunkedBytes(byte[] initialData) { + this(); + this.append(initialData, 0, initialData.length); + } + + public ChunkedBytes(String initialData) { + this(); + this.append(UTF8.getBytes(initialData)); + } + + /** Represents one contiguous region in the logical address space. */ + private static final class Segment implements Closeable { + final Chunk chunk; + final long start; // global start offset + final int length; // length within this segment (<= CHUNK_SIZE) + Segment(Chunk chunk, long start, int length) { + this.chunk = chunk; this.start = start; this.length = length; + } + @Override public void close() throws IOException { this.chunk.close(); } + } + + /** Common interface for heap/file-backed chunks. */ + private interface Chunk extends Closeable { + int read(long relPos, byte[] dst, int off, int len); + int write(long relPos, byte[] src, int off, int len); + byte get(long relPos); + void set(long relPos, byte b); + int length(); + @Override default void close() { /* no-op by default */ } + } + + /** On-heap chunk. */ + private static final class HeapChunk implements Chunk { + final byte[] buf; + HeapChunk(int cap) { + assert cap > 0 && cap <= CHUNK_SIZE : "Invalid HeapChunk capacity: " + cap; + this.buf = new byte[cap]; + } + @Override public int read(long p, byte[] dst, int off, int len) { + final int pos = (int)p; final int n = Math.min(len, this.buf.length - pos); + if (n <= 0) return -1; + System.arraycopy(this.buf, pos, dst, off, n); + return n; + } + @Override public int write(long p, byte[] src, int off, int len) { + final int pos = (int)p; final int n = Math.min(len, this.buf.length - pos); + if (n <= 0) return -1; + System.arraycopy(src, off, this.buf, pos, n); + return n; + } + @Override public byte get(long p) { return this.buf[(int)p]; } + @Override public void set(long p, byte b) { this.buf[(int)p] = b; } + @Override public int length() { return this.buf.length; } + } + + /** File-backed chunk using MappedByteBuffer (lazy mapped). */ + private static final class FileChunk implements Chunk { + final FileChannel ch; + final long fileOffset; // offset in the file where this chunk starts + final int len; + final boolean writable; + private volatile MappedByteBuffer mm; // lazily created + + FileChunk(FileChannel ch, long fileOffset, int len, boolean writable) { + this.ch = ch; this.fileOffset = fileOffset; this.len = len; this.writable = writable; + } + private MappedByteBuffer map() { + MappedByteBuffer local = this.mm; + if (local == null) { + synchronized (this) { + local = this.mm; + if (local == null) { + final FileChannel.MapMode mapMode = this.writable ? FileChannel.MapMode.READ_WRITE : FileChannel.MapMode.READ_ONLY; + try { + this.mm = local = this.ch.map(mapMode, this.fileOffset, this.len); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + } + } + return local; + } + @Override public int read(long p, byte[] dst, int off, int len) { + if (p >= this.len) return -1; + final int take = Math.min(len, this.len - (int)p); + final MappedByteBuffer dup = this.map().duplicate(); + dup.position((int)p).limit((int)p + take); + dup.get(dst, off, take); + return take; + } + @Override public int write(long p, byte[] src, int off, int len) { + if (!this.writable) throw new RuntimeException("FileChunk is read-only"); + if (p >= this.len) return -1; + final int take = Math.min(len, this.len - (int)p); + final MappedByteBuffer dup = this.map().duplicate(); + dup.position((int)p).limit((int)p + take); + dup.put(src, off, take); + return take; + } + @Override public byte get(long p) { + return this.map().get((int)p); + } + @Override public void set(long p, byte b) { + if (!this.writable) throw new RuntimeException("FileChunk is read-only"); + this.map().put((int)p, b); + } + @Override public int length() { return this.len; } + @Override public void close() { + // Best-effort explicit unmap to release file handles promptly. + final MappedByteBuffer local = this.mm; + if (local != null) { + try { Unmapper.unmap(local); } catch (final Throwable ignored) {} + } + // Channel is closed by owner (we don’t own it here). + } + } + + /** Best-effort unmapper compatible with Java 8+ (Unsafe.invokeCleaner fallback). */ + private static final class Unmapper { + + static void unmap(MappedByteBuffer bb) throws Exception { + // Java 9+: Unsafe.invokeCleaner + try { + final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); + final var theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + final Object unsafe = theUnsafe.get(null); + unsafeClass.getMethod("invokeCleaner", MappedByteBuffer.class).invoke(unsafe, bb); + return; + } catch (final Throwable ignore) {} + // Java 8: DirectBuffer.cleaner().clean() + final Class<?> directBuffer = Class.forName("sun.nio.ch.DirectBuffer"); + final Object db = directBuffer.cast(bb); + final Object cleaner = directBuffer.getMethod("cleaner").invoke(db); + if (cleaner != null) cleaner.getClass().getMethod("clean").invoke(cleaner); + } + + } + + // ------------------- public API ------------------- + + /** Total logical size in bytes. */ + public long size() { return this.size; } + + /** Append bytes from an InputStream into on-heap chunks. */ + public void writeFrom(InputStream in) throws IOException { + final byte[] tmp = new byte[64 * 1024]; + int r; + while ((r = in.read(tmp)) != -1) { + // InputStream.read(byte[]) may legally return 0 + if (r == 0) continue; + this.append(tmp, 0, r); + } + + } + + /** Append a byte array (copied into on-heap chunks). */ + public void append(byte[] src, int off, int len) { + while (len > 0) { + int space = this.spaceInTailHeapChunk(); + if (space == 0) { + // size the new chunk to what we need now (up to CHUNK_SIZE) + this.newTailHeapChunk(len); + space = this.spaceInTailHeapChunk(); + } + final Segment tail = this.segments.get(this.segments.size() - 1); + + final int take = Math.min(len, space); + + // WRITE AT CURRENT USED LENGTH IN THE TAIL CHUNK (not length - space) + final int written = tail.chunk.write(tail.length, src, off, take); + if (written <= 0) break; // defensive; shouldn't happen with HeapChunk + + this.growTailLength(tail, written); + off += written; + len -= written; + this.size += written; + } + } + + public void append(byte[] src) { + this.append(src, 0, src.length); + } + + public void append(String src, int off, int len) { + this.append(src.substring(off, off + len)); + } + + public void append(String src) { + this.append(UTF8.getBytes(src)); + } + + /** Adopt an entire file as zero-copy file-backed segments (read-only). */ + public void appendFile(Path path) { + this.appendFile(path, false); + } + + /** Adopt a file; set writable=true to allow modifications to the mapped bytes. */ + public void appendFile(Path path, boolean writable) { + FileChannel ch = null; + try { + ch = FileChannel.open(path, writable + ? new OpenOption[]{StandardOpenOption.READ, StandardOpenOption.WRITE} + : new OpenOption[]{StandardOpenOption.READ}); + final long fileSize = ch.size(); + long pos = 0; + while (pos < fileSize) { + final int len = (int)Math.min(CHUNK_SIZE, fileSize - pos); + this.segments.add(new Segment(new FileChunk(ch, pos, len, writable), this.size, len)); + this.size += len; + pos += len; + } + // We keep the FileChannel open until this ChunkedBytes is closed. + this.fileChannels.add(ch); + ch = null; // prevent closing in finally + } catch (final IOException e) { + throw new RuntimeException(e); + } finally { + if (ch != null) try { ch.close(); } catch (final IOException ignore) {} + } + } + + /** Read into dst starting at logical position pos. Returns bytes read or -1 at EOF. */ + public int read(long pos, byte[] dst, int off, int len) { + if (pos < 0) throw new IllegalArgumentException("pos < 0"); + if (pos >= this.size) return -1; + long remaining = Math.min(len, this.size - pos); + int done = 0; + int idx = this.findSegment(pos); + long p = pos; + while (remaining > 0 && idx < this.segments.size()) { + final Segment s = this.segments.get(idx); + final long rel = p - s.start; + final int take = (int)Math.min(remaining, s.length - rel); + final int n = s.chunk.read(rel, dst, off + done, take); + if (n <= 0) break; + done += n; p += n; remaining -= n; + if (rel + n >= s.length) idx++; + } + return done == 0 ? -1 : done; + } + + /** Write bytes at logical position pos (requires writable backing for those ranges). */ + public int write(long pos, byte[] src, int off, int len) { + if (pos < 0) throw new IllegalArgumentException("pos < 0"); + if (pos >= this.size) return -1; + long remaining = Math.min(len, this.size - pos); + int done = 0; int idx = this.findSegment(pos); long p = pos; + while (remaining > 0 && idx < this.segments.size()) { + final Segment s = this.segments.get(idx); + final long rel = p - s.start; + final int take = (int)Math.min(remaining, s.length - rel); + final int n = s.chunk.write(rel, src, off + done, take); + if (n <= 0) break; + done += n; p += n; remaining -= n; + if (rel + n >= s.length) idx++; + } + return done == 0 ? -1 : done; + } + + /** InputStream view (no copying), supports >2 GB. */ + public InputStream openStream() { + return new InputStream() { + long pos = 0; + @Override public int read() throws IOException { + final byte[] one = new byte[1]; + final int n = this.read(one, 0, 1); + return n < 0 ? -1 : (one[0] & 0xFF); + } + @Override public int read(byte[] b, int off, int len) throws IOException { + final int n = ChunkedBytes.this.read(this.pos, b, off, len); + if (n > 0) this.pos += n; + return n; + } + @Override public long skip(long n) { + final long k = Math.min(n, ChunkedBytes.this.size - this.pos); + this.pos += k; return k; + } + @Override public int available() { + final long rem = ChunkedBytes.this.size - this.pos; + return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; + } + }; + } + + /** Write the whole content to an OutputStream. */ + public void writeTo(OutputStream out) { + final byte[] tmp = new byte[256 * 1024]; + long p = 0; + try { + while (p < this.size) { + final int n = this.read(p, tmp, 0, tmp.length); + if (n < 0) break; + out.write(tmp, 0, n); + p += n; + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + /** Materialize as a single byte[] (only if total size fits in an int). */ + public byte[] toByteArray() { + if (this.size > Integer.MAX_VALUE) throw new RuntimeException("Size > Integer.MAX_VALUE"); + final byte[] all = new byte[(int) this.size]; + this.writeTo(new ByteArrayOutputStream() { + int offset = 0; + @Override public void write(byte[] b, int off, int len) { + System.arraycopy(b, off, all, this.offset, len); + this.offset += len; + } + }); + return all; + } + + @Override + public synchronized void write(int b) { + // Append single byte at end (OutputStream semantics) + final byte[] one = new byte[] { (byte) b }; + this.append(one, 0, 1); + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + if (b == null) throw new NullPointerException("b"); + if (off < 0 || len < 0 || off + len > b.length) throw new IndexOutOfBoundsException(); + // Append to end; grows with heap chunks as needed + this.append(b, off, len); + } + + public void writeBytes(byte[] b) { + this.write(b, 0, b.length); + } + + @Override + public void flush() { + // No-op for heap chunks; file-backed segments via MappedByteBuffer are + // written eagerly. If you need a hard sync to disk, expose a separate + // method to force() mapped buffers. + } + + @Override public void close() { + // Close/unmap all segments and channels. + Exception first = null; + for (final Segment s : this.segments) { + try { s.close(); } catch (final Exception e) { if (first == null) first = e; } + } + for (final FileChannel ch : this.fileChannels) { + try { ch.close(); } catch (final Exception e) { if (first == null) first = e; } + } + if (first != null) throw new RuntimeException(first.getMessage()); + } + + @Override + public String toString() { + return UTF8.String(this.toByteArray()); + } + + public byte get(long pos) { + if (pos < 0) throw new IllegalArgumentException("pos < 0"); + if (pos >= this.size) throw new RuntimeException("pos >= size"); + final int idx = this.findSegment(pos); + final Segment s = this.segments.get(idx); + final long rel = pos - s.start; + return s.chunk.get(rel); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ChunkedBytes) { + final ChunkedBytes cb = (ChunkedBytes)o; + if (this.size != cb.size) return false; + for (long i = 0; i < this.size; i++) { + if (this.get(i) != cb.get(i)) return false; + } + return true; + } + if (o instanceof byte[]) { + final byte[] b = (byte[])o; + if (this.size != b.length) return false; + for (int i = 0; i < b.length; i++) { + if (this.get(i) != b[i]) return false; + } + return true; + } + if (o instanceof String) { + return this.equals(UTF8.getBytes((String) o)); + } + return false; + } + + @Override + public int compareTo(Object o) { + if (o instanceof ChunkedBytes) { + final ChunkedBytes cb = (ChunkedBytes)o; + final int minLen = (int)Math.min(this.size, cb.size); + for (int i = 0; i < minLen; i++) { + final int diff = (this.get(i) & 0xFF) - (cb.get(i) & 0xFF); + if (diff != 0) return diff; + } + return Long.compare(this.size, cb.size); + } + if (o instanceof byte[]) { + final byte[] b = (byte[])o; + final int minLen = (int)Math.min(this.size, b.length); + for (int i = 0; i < minLen; i++) { + final int diff = (this.get(i) & 0xFF) - (b[i] & 0xFF); + if (diff != 0) return diff; + } + return Long.compare(this.size, b.length); + } + if (o instanceof String) { + return this.compareTo(UTF8.getBytes((String) o)); + } + throw new IllegalArgumentException("Cannot compare to " + (o == null ? "null" : o.getClass().getName())); + } + + // ------------------- internals ------------------- + + private final List<FileChannel> fileChannels = new ArrayList<>(); + + private int findSegment(long pos) { + int lo = 0, hi = this.segments.size() - 1; + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final Segment s = this.segments.get(mid); + if (pos < s.start) hi = mid - 1; + else if (pos >= s.start + s.length) lo = mid + 1; + else return mid; + } + return Math.max(0, Math.min(lo, this.segments.size() - 1)); + } + + private int spaceInTailHeapChunk() { + if (this.segments.isEmpty()) return 0; + final Segment tail = this.segments.get(this.segments.size() - 1); + if (!(tail.chunk instanceof HeapChunk)) return 0; + return tail.length < tail.chunk.length() ? tail.chunk.length() - tail.length : 0; + } + + private void newTailHeapChunk(int minCapacity) { + final int cap = Math.min(CHUNK_SIZE, minCapacity); + this.segments.add(new Segment(new HeapChunk(cap), this.size, 0)); + } + + private void growTailLength(Segment tail, int inc) { + // We cannot actually change 'length' as it's final; create a new Segment with updated length. + final int idx = this.segments.size() - 1; + this.segments.set(idx, new Segment(tail.chunk, tail.start, tail.length + inc)); + } + + // === Add inside ChunkedBytes class === + public static void main(String[] args) throws Exception { + System.out.println("ChunkedBytes test starting. CHUNK_SIZE=" + (CHUNK_SIZE / (1024*1024)) + " MiB"); + + // --------- Parameters ---------- + final long seed = 0x5eedCafeL; + final long bigLen = 5L * CHUNK_SIZE + (CHUNK_SIZE / 2) + 12345; // > 5 chunks + final int ioBuf = 1 << 20; // 1 MiB streaming buffer + + // Prepare sample offsets across boundaries + final long[] samples = new long[] { + 0L, + CHUNK_SIZE - 1L, + CHUNK_SIZE, + CHUNK_SIZE + 1L, + 3L * CHUNK_SIZE - 1L, + 3L * CHUNK_SIZE, + 5L * CHUNK_SIZE + 17L, + bigLen - 1L + }; + + // ========================= + // A) HEAP-ONLY APPEND TESTS + // ========================= + try (ChunkedBytes cb = new ChunkedBytes()) { + System.out.println("[A] Heap-only append of random " + bigLen + " bytes (>5 chunks) via OutputStream"); + + // Fill with random data using OutputStream semantics and collect digest + expected sample bytes + final byte[] expectedSample = new byte[samples.length]; + final byte[] buf = new byte[ioBuf]; + final java.util.Random rnd = new java.util.Random(seed); + final java.security.MessageDigest mdIn = java.security.MessageDigest.getInstance("SHA-256"); + + long pos = 0; + int si = 0; + while (pos < bigLen) { + final int n = (int)Math.min(buf.length, bigLen - pos); + rnd.nextBytes(buf); + mdIn.update(buf, 0, n); + // write using OutputStream.write(byte[],off,len) + cb.write(buf, 0, n); + + // capture sample bytes as we stream + while (si < samples.length && samples[si] >= pos && samples[si] < pos + n) { + expectedSample[si] = buf[(int)(samples[si] - pos)]; + si++; + } + pos += n; + } + final byte[] digestOriginal = mdIn.digest(); + System.out.println(" Original SHA-256: " + toHex(digestOriginal)); + + // Validate size + assertEquals(bigLen, cb.size(), "[A] size"); + + // Digest of content read back + final byte[] digestCb = sha256Of(cb.openStream(), ioBuf); + System.out.println(" CB stream SHA-256: " + toHex(digestCb)); + assertArrayEquals(digestOriginal, digestCb, "[A] digest equality"); + + // Validate sample bytes via random-access read() + for (int i = 0; i < samples.length; i++) { + final byte b = readByteAt(cb, samples[i]); + if (b != expectedSample[i]) { + throw new AssertionError("[A] sample mismatch at " + samples[i]); + } + } + System.out.println(" Sample point checks: OK"); + + // Test read spanning a boundary + final long crossStart = CHUNK_SIZE - 2L; + final byte[] got = new byte[5]; + final int n = cb.read(crossStart, got, 0, got.length); + assertEquals(5, n, "[A] cross-boundary read length"); + final byte[] expect = regenRange(seed, bigLen, crossStart, 5, ioBuf); + assertArrayEquals(expect, got, "[A] cross-boundary bytes"); + + // Test write(long pos, byte[]...) modifying content and verifying + final byte[] patch = new byte[] {99, 98, 97, 0, 1, 2, 3}; + final long patchPos = 2L * CHUNK_SIZE + 7; + final int wrote = cb.write(patchPos, patch, 0, patch.length); + assertEquals(patch.length, wrote, "[A] write length"); + final byte[] check = new byte[patch.length]; + final int rn = cb.read(patchPos, check, 0, check.length); + assertEquals(patch.length, rn, "[A] reread length"); + assertArrayEquals(patch, check, "[A] write verification"); + + // Test InputStream skip/available/EoF + try (InputStream in = cb.openStream()) { + final long skipped = in.skip(patchPos); + assertEquals(patchPos, skipped, "[A] skip"); + final int avail = in.available(); + if (avail <= 0) throw new AssertionError("[A] available should be > 0 after skip"); + final byte[] tmp = in.readNBytes(32); + if (tmp.length == 0) throw new AssertionError("[A] read after skip failed"); + // drain + while (in.read(tmp) >= 0) { /* drain */ } + if (in.read() != -1) throw new AssertionError("[A] EOF expected"); + } + + // Test writeTo(OutputStream) into a digest sink + final byte[] digestAfter = sha256Of(cb.openStream(), ioBuf); + final java.security.MessageDigest mdSink = java.security.MessageDigest.getInstance("SHA-256"); + cb.writeTo(new java.security.DigestOutputStream(new NullOutputStream(), mdSink)); + final byte[] digestWriteTo = mdSink.digest(); + assertArrayEquals(digestAfter, digestWriteTo, "[A] writeTo digest (post-mutation)"); + + // Small dataset to test toByteArray() + try (ChunkedBytes small = new ChunkedBytes()) { + final byte[] sm = new byte[15000]; + new java.util.Random(123).nextBytes(sm); + small.write(sm); // OutputStream API + final byte[] smOut = small.toByteArray(); + assertArrayEquals(sm, smOut, "[A] toByteArray"); + } + + System.out.println("[A] Heap-only tests: OK"); + } + + // ========================================= + // B) FILE-BACKED MAPPING (READ-ONLY) TESTS + // ========================================= + final Path tmpFile = Files.createTempFile("cb-ro-", ".bin"); + try { + final long fileLen = 3L * CHUNK_SIZE + 12345; + System.out.println("[B] Create temp file (read-only mapping) len=" + fileLen); + byte[] fileDigest; + try (OutputStream fout = Files.newOutputStream(tmpFile)) { + fileDigest = writeRandomToStream(fout, seed + 1, fileLen, ioBuf); + } + System.out.println(" File SHA-256: " + toHex(fileDigest)); + + try (ChunkedBytes cb = new ChunkedBytes()) { + cb.appendFile(tmpFile); // read-only map + assertEquals(fileLen, cb.size(), "[B] size"); + final byte[] cbDigest = sha256Of(cb.openStream(), ioBuf); + System.out.println(" CB map SHA-256: " + toHex(cbDigest)); + assertArrayEquals(fileDigest, cbDigest, "[B] digest equality"); + + // spot check boundary + final long pos = CHUNK_SIZE - 3; + final byte[] got = new byte[9]; + final int m = cb.read(pos, got, 0, got.length); + assertEquals(9, m, "[B] boundary read length"); + final byte[] exp = regenRange(seed + 1, fileLen, pos, 9, ioBuf); + assertArrayEquals(exp, got, "[B] boundary bytes"); + } + System.out.println("[B] Read-only mapping tests: OK"); + } finally { + try { Files.deleteIfExists(tmpFile); } catch (final Exception ignore) {} + } + + // ========================================= + // C) FILE-BACKED MAPPING (WRITABLE) TESTS + // ========================================= + final Path tmpRW = Files.createTempFile("cb-rw-", ".bin"); + try { + final long fileLen = 2L * CHUNK_SIZE + 777; + System.out.println("[C] Create temp file (writable mapping) len=" + fileLen); + try (OutputStream fout = Files.newOutputStream(tmpRW)) { + writeRandomToStream(fout, seed + 2, fileLen, ioBuf); + } + + try (ChunkedBytes cb = new ChunkedBytes()) { + cb.appendFile(tmpRW, true); // writable + // Modify three places: start, boundary, end-5 + final long[] offs = new long[] { 0L, CHUNK_SIZE, fileLen - 5 }; + final byte[][] patches = new byte[][] { + {7,6,5,4,3}, + {1,2,3,4}, + {-1,-2,-3,-4,-5} + }; + for (int i = 0; i < offs.length; i++) { + final int w = cb.write(offs[i], patches[i], 0, patches[i].length); + assertEquals(patches[i].length, w, "[C] write length " + i); + final byte[] chk = new byte[patches[i].length]; + final int r = cb.read(offs[i], chk, 0, chk.length); + assertEquals(chk.length, r, "[C] reread len " + i); + assertArrayEquals(patches[i], chk, "[C] content verify " + i); + } + } + // Verify on-disk after close() + try (var ch = FileChannel.open(tmpRW, StandardOpenOption.READ)) { + final byte[] p0 = new byte[5]; readFully(ch, 0L, p0); + assertArrayEquals(new byte[]{7,6,5,4,3}, p0, "[C] disk verify 0"); + final byte[] p1 = new byte[4]; readFully(ch, CHUNK_SIZE, p1); + assertArrayEquals(new byte[]{1,2,3,4}, p1, "[C] disk verify 1"); + final byte[] p2 = new byte[5]; readFully(ch, (2L * CHUNK_SIZE + 777) - 5, p2); + assertArrayEquals(new byte[]{-1,-2,-3,-4,-5}, p2, "[C] disk verify 2"); + } + System.out.println("[C] Writable mapping tests: OK"); + } finally { + try { Files.deleteIfExists(tmpRW); } catch (final Exception ignore) {} + } + + // ========================== + // D) MIXED SOURCES TESTS + // ========================== + System.out.println("[D] Mixed sources (heap + file + single-byte writes)"); + final Path tmpMix = Files.createTempFile("cb-mix-", ".bin"); + // prepare file contents used in this test + final long mixLen = CHUNK_SIZE + 333; + try (OutputStream fout = Files.newOutputStream(tmpMix)) { + writeRandomToStream(fout, seed + 3, mixLen, ioBuf); + } + try (ChunkedBytes cb = new ChunkedBytes()) { + // 1) small heap prefix + final byte[] prefix = new byte[5000]; + new java.util.Random(42).nextBytes(prefix); + cb.write(prefix); // OutputStream API + + // 2) file segment + cb.appendFile(tmpMix); // read-only + + // 3) a tail written byte-by-byte + for (int i = 0; i < 1000; i++) cb.write(i & 0xFF); + + // Verify size + final long expectedSize = prefix.length + Files.size(tmpMix) + 1000L; + assertEquals(expectedSize, cb.size(), "[D] size"); + + // Spot checks + // prefix region + final byte[] got = new byte[prefix.length]; + final int r = cb.read(0, got, 0, got.length); + assertEquals(prefix.length, r, "[D] prefix read len"); + assertArrayEquals(prefix, got, "[D] prefix bytes"); + + // file region slice + final byte[] fileSliceExpected = regenRange(seed + 3, Files.size(tmpMix), 123, 256, ioBuf); + final byte[] fileSliceGot = new byte[256]; + final int r2 = cb.read(prefix.length + 123, fileSliceGot, 0, fileSliceGot.length); + assertEquals(256, r2, "[D] file slice len"); + assertArrayEquals(fileSliceExpected, fileSliceGot, "[D] file slice bytes"); + + // tail region last 10 + final byte[] tail = new byte[10]; + final int r3 = cb.read(expectedSize - 10, tail, 0, 10); + assertEquals(10, r3, "[D] tail len"); + for (int i = 0; i < 10; i++) { + final byte exp = (byte)((1000 - 10) + i & 0xFF); + if (tail[i] != exp) throw new AssertionError("[D] tail byte mismatch at i=" + i); + } + + // writeTo digest equals digest of concatenation? We can't easily combine digests here; + // just ensure writeTo writes full length by counting bytes. + final CountingOutputStream cos = new CountingOutputStream(); + cb.writeTo(cos); + assertEquals(expectedSize, cos.count, "[D] writeTo count"); + } finally { + try { Files.deleteIfExists(tmpMix); } catch (final Exception ignore) {} + } + System.out.println("[D] Mixed sources tests: OK"); + + System.out.println("All tests PASSED."); + } + + // ----- helpers ----- + + private static final class NullOutputStream extends OutputStream { + @Override public void write(int b) {} + @Override public void write(byte[] b, int off, int len) {} + } + + private static final class CountingOutputStream extends OutputStream { + long count = 0; + @Override public void write(int b) { this.count++; } + @Override public void write(byte[] b, int off, int len) { this.count += len; } + } + + private static void assertEquals(long exp, long got, String where) { + if (exp != got) throw new AssertionError(where + ": expected " + exp + " but got " + got); + } + private static void assertEquals(int exp, int got, String where) { + if (exp != got) throw new AssertionError(where + ": expected " + exp + " but got " + got); + } + private static void assertArrayEquals(byte[] exp, byte[] got, String where) { + if (!java.util.Arrays.equals(exp, got)) { + throw new AssertionError(where + ": arrays differ"); + } + } + private static String toHex(byte[] d) { + final StringBuilder sb = new StringBuilder(d.length * 2); + for (final byte b : d) sb.append(String.format("%02x", b)); + return sb.toString(); + } + private static byte[] sha256Of(InputStream in, int bufSize) throws Exception { + final java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + final byte[] buf = new byte[bufSize]; + int n; + while ((n = in.read(buf)) >= 0) md.update(buf, 0, n); + return md.digest(); + } + private static byte readByteAt(ChunkedBytes cb, long pos) throws IOException { + final byte[] one = new byte[1]; + final int n = cb.read(pos, one, 0, 1); + if (n != 1) throw new IOException("Unable to read at pos=" + pos); + return one[0]; + } + private static byte[] regenRange(long seed, long totalLen, long start, int len, int bufSize) throws IOException { + if (start + len > totalLen) throw new IOException("range exceeds totalLen"); + final java.util.Random rnd = new java.util.Random(seed); + final byte[] buf = new byte[bufSize]; + long pos = 0; + final byte[] out = new byte[len]; + int outPos = 0; + while (pos < totalLen && outPos < len) { + final int n = (int)Math.min(buf.length, totalLen - pos); + rnd.nextBytes(buf); + final long end = pos + n; + if (start < end && (start + len) > pos) { + final long s = Math.max(start, pos); + final long e = Math.min(start + len, end); + final int copy = (int)(e - s); + System.arraycopy(buf, (int)(s - pos), out, outPos, copy); + outPos += copy; + } + pos = end; + } + return out; + } + private static byte[] writeRandomToStream(OutputStream out, long seed, long length, int bufSize) throws Exception { + final java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + final java.util.Random rnd = new java.util.Random(seed); + final byte[] buf = new byte[bufSize]; + long pos = 0; + while (pos < length) { + final int n = (int)Math.min(buf.length, length - pos); + rnd.nextBytes(buf); + out.write(buf, 0, n); + md.update(buf, 0, n); + pos += n; + } + out.flush(); + return md.digest(); + } + private static void readFully(FileChannel ch, long pos, byte[] dst) throws IOException { + final ByteBuffer bb = ByteBuffer.wrap(dst); + while (bb.hasRemaining()) { + final int n = ch.read(bb, pos); + if (n < 0) throw new EOFException(); + pos += n; + } + } + +} diff --git a/source/net/yacy/crawler/CrawlStacker.java b/source/net/yacy/crawler/CrawlStacker.java index e62bfaad2..c27185883 100644 --- a/source/net/yacy/crawler/CrawlStacker.java +++ b/source/net/yacy/crawler/CrawlStacker.java @@ -65,6 +65,7 @@ public final class CrawlStacker implements WorkflowTask<Request>{ public static String ERROR_NO_MATCH_MUST_MATCH_FILTER = "url does not match must-match filter ";
public static String ERROR_MATCH_WITH_MUST_NOT_MATCH_FILTER = "url matches must-not-match filter ";
+ public static String ERROR_REDIRECT = "Redirect of ";
/** Crawl reject reason prefix having specific processing */
public static final String CRAWL_REJECT_REASON_DOUBLE_IN_PREFIX = "double in";
@@ -124,11 +125,11 @@ public final class CrawlStacker implements WorkflowTask<Request>{ public synchronized void close() {
CrawlStacker.log.info("Shutdown. waiting for remaining " + this.size() + " crawl stacker job entries. please wait.");
this.requestQueue.shutdown();
-
+
// busy waiting for the queue to empty
for (int i = 0; i < 10; i++) {
if (this.size() <= 0) break;
- try {Thread.sleep(1000);} catch (InterruptedException e) {}
+ try {Thread.sleep(1000);} catch (final InterruptedException e) {}
}
CrawlStacker.log.info("Shutdown. Closing stackCrawl queue.");
diff --git a/source/net/yacy/data/BlogBoard.java b/source/net/yacy/data/BlogBoard.java index 9b28dc1b9..bb026fea5 100644 --- a/source/net/yacy/data/BlogBoard.java +++ b/source/net/yacy/data/BlogBoard.java @@ -31,6 +31,7 @@ package net.yacy.data; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.text.ParseException; import java.util.ArrayList; import java.util.Comparator; @@ -47,6 +48,11 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + import net.yacy.cora.date.GenericFormatter; import net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.order.Base64Order; @@ -58,11 +64,6 @@ import net.yacy.data.wiki.WikiBoard; import net.yacy.kelondro.blob.MapHeap; import net.yacy.kelondro.util.kelondroException; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - public class BlogBoard { @@ -138,7 +139,7 @@ public class BlogBoard { } public BlogEntry readBlogEntry(final String key) { - return readBlogEntry(key, this.database); + return this.readBlogEntry(key, this.database); } private BlogEntry readBlogEntry(final String key, final MapHeap base) { @@ -154,7 +155,7 @@ public class BlogBoard { record = null; } return (record == null) ? - newEntry(key, new byte[0], UTF8.getBytes("anonymous"), Domains.LOCALHOST, new Date(), new byte[0], null, null) : + this.newEntry(key, new byte[0], UTF8.getBytes("anonymous"), Domains.LOCALHOST, new Date(), new byte[0], null, null) : new BlogEntry(key, record); } @@ -163,7 +164,7 @@ public class BlogBoard { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { final DocumentBuilder builder = factory.newDocumentBuilder(); - return parseXMLimport(builder.parse(new ByteArrayInputStream(UTF8.getBytes(input)))); + return this.parseXMLimport(builder.parse(new ByteArrayInputStream(UTF8.getBytes(input)))); } catch (final ParserConfigurationException ex) { ConcurrentLog.logException(ex); } catch (final SAXException ex) { @@ -175,6 +176,23 @@ public class BlogBoard { return false; } + public boolean importXML(final InputStream is) { + if (is != null) { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = factory.newDocumentBuilder(); + return this.parseXMLimport(builder.parse(is)); + } catch (final ParserConfigurationException ex) { + ConcurrentLog.logException(ex); + } catch (final SAXException ex) { + ConcurrentLog.logException(ex); + } catch (final IOException ex) { + ConcurrentLog.logException(ex); + } + } + return false; + } + private boolean parseXMLimport(final Document doc) { if(!"blog".equals(doc.getDocumentElement().getTagName())) { return false; @@ -224,7 +242,7 @@ public class BlogBoard { subject = UTF8.getBytes(StrSubject); author = UTF8.getBytes(StrAuthor); page = UTF8.getBytes(StrPage); - writeBlogEntry (newEntry(key, subject, author, ip, date, page, null, null)); + this.writeBlogEntry (this.newEntry(key, subject, author, ip, date, page, null, null)); } return true; } @@ -255,8 +273,8 @@ public class BlogBoard { @Override public int compare(final String obj1, final String obj2) { - final BlogEntry blogEntry1 = readBlogEntry(obj1); - final BlogEntry blogEntry2 = readBlogEntry(obj2); + final BlogEntry blogEntry1 = BlogBoard.this.readBlogEntry(obj1); + final BlogEntry blogEntry2 = BlogBoard.this.readBlogEntry(obj2); if (blogEntry1 == null || blogEntry2 == null) return 0; if (this.newestFirst) { @@ -273,8 +291,8 @@ public class BlogBoard { } public Iterator<String> getBlogIterator(final boolean priv){ - final Set<String> set = new TreeSet<String>(new BlogComparator(true)); - final Iterator<BlogEntry> iterator = blogIterator(true); + final Set<String> set = new TreeSet<>(new BlogComparator(true)); + final Iterator<BlogEntry> iterator = this.blogIterator(true); BlogEntry blogEntry; while (iterator.hasNext()) { blogEntry = iterator.next(); @@ -317,7 +335,7 @@ public class BlogBoard { @Override public BlogEntry next() { try { - return readBlogEntry(UTF8.String(this.blogIter.next())); + return BlogBoard.this.readBlogEntry(UTF8.String(this.blogIter.next())); } catch (final kelondroException e) { //resetDatabase(); return null; @@ -344,15 +362,15 @@ public class BlogBoard { Map<String, String> record; public BlogEntry(final String nkey, final byte[] subject, final byte[] author, final String ip, final Date date, final byte[] page, final List<String> comments, final String commentMode) { - this.record = new HashMap<String, String>(); - setKey(nkey); - setDate(date); - setSubject(subject); - setAuthor(author); - setIp(ip); - setPage(page); - setComments(comments); - setCommentMode(commentMode); + this.record = new HashMap<>(); + this.setKey(nkey); + this.setDate(date); + this.setSubject(subject); + this.setAuthor(author); + this.setIp(ip); + this.setPage(page); + this.setComments(comments); + this.setCommentMode(commentMode); // TODO: implement this function this.record.put("privacy", "public"); @@ -364,7 +382,7 @@ public class BlogBoard { this.key = key; this.record = record; if (this.record.get("comments") == null) { - this.record.put("comments", ListManager.collection2string(new ArrayList<String>())); + this.record.put("comments", ListManager.collection2string(new ArrayList<>())); } if (this.record.get("commentMode") == null || this.record.get("commentMode").length() < 1) { this.record.put("commentMode", "2"); @@ -450,7 +468,7 @@ public class BlogBoard { // This ist a Bugfix for Version older than 4443. if (this.record.get("comments").startsWith(",")) { this.record.put("comments", this.record.get("comments").substring(1)); - writeBlogEntry(this); + BlogBoard.this.writeBlogEntry(this); } final List<String> commentsize = ListManager.string2arraylist(this.record.get("comments")); return commentsize.size(); @@ -462,7 +480,7 @@ public class BlogBoard { private void setComments(final List<String> comments) { if (comments == null) { - this.record.put("comments", ListManager.collection2string(new ArrayList<String>())); + this.record.put("comments", ListManager.collection2string(new ArrayList<>())); } else { this.record.put("comments", ListManager.collection2string(comments)); } diff --git a/source/net/yacy/data/TransactionManager.java b/source/net/yacy/data/TransactionManager.java index c0e12fc7d..73930bc16 100644 --- a/source/net/yacy/data/TransactionManager.java +++ b/source/net/yacy/data/TransactionManager.java @@ -96,7 +96,7 @@ public class TransactionManager { /** * Get a transaction token to be used later on a protected HTTP post method * call on the same path with the currently authenticated user. - * + * * @param header * current request header * @return a transaction token @@ -114,7 +114,7 @@ public class TransactionManager { /** * Get a transaction token to be used later on a protected HTTP post method * call on the specified path with the currently authenticated user. - * + * * @param header * current request header * @param path the relative path for which the token will be valid @@ -133,10 +133,10 @@ public class TransactionManager { throw new IllegalArgumentException("User is not authenticated"); } - /* Produce a token by signing a message with the server secret key : - * The token is not unique per request and thus keeps the service stateless + /* Produce a token by signing a message with the server secret key : + * The token is not unique per request and thus keeps the service stateless * (no need to store tokens until they are consumed). - * On the other hand, it is supposed to remain hard enough to forge because the secret key and token seed + * On the other hand, it is supposed to remain hard enough to forge because the secret key and token seed * are initialized with a random value at each server startup */ final String token = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, SIGNING_KEY) .hmacHex(TOKEN_SEED + userName + path); @@ -145,7 +145,7 @@ public class TransactionManager { } /** - * Check the current request is a valid HTTP POST transaction : the current user is authenticated, + * Check the current request is a valid HTTP POST transaction : the current user is authenticated, * and the request post parameters contain a valid transaction token. * @param header current request header * @param post request parameters @@ -170,14 +170,14 @@ public class TransactionManager { if (userName == null) throw new BadTransactionException("User is not authenticated."); - final String transactionToken = post.get(TRANSACTION_TOKEN_PARAM); + final String transactionToken = post.get(TRANSACTION_TOKEN_PARAM, ""); if (transactionToken == null) throw new TemplateMissingParameterException("Missing transaction token."); final String token = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, SIGNING_KEY) .hmacHex(TOKEN_SEED + userName + header.getPathInfo()); - /* Compare the server generated token with the one received in the post parameters, + /* Compare the server generated token with the one received in the post parameters, * using a time constant function */ if(!MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8), transactionToken.getBytes(StandardCharsets.UTF_8))) { throw new BadTransactionException("Invalid transaction token."); diff --git a/source/net/yacy/data/WorkTables.java b/source/net/yacy/data/WorkTables.java index 0af5baf55..c4fdff705 100644 --- a/source/net/yacy/data/WorkTables.java +++ b/source/net/yacy/data/WorkTables.java @@ -107,30 +107,30 @@ public class WorkTables extends Tables { * but prevents revealing it in the URL displayed in the process scheduler and prevents storing an outdated value */ final String transactionToken; if(post != null) { - transactionToken = post.get(TransactionManager.TRANSACTION_TOKEN_PARAM); + transactionToken = post.get(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); } else { - transactionToken = null; + transactionToken = null; } if(transactionToken != null && post != null) { - post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); + post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); } // generate the apicall url - without the apicall attributes - String apiurl = "/" + servletName; + StringBuilder apiurl = new StringBuilder("/").append(servletName); if(post != null) { - apiurl += "?" + post.toString(); + apiurl.append("?").append(post.toString()); } /* Now restore the eventual transaction token to prevent side effects on the post object eventually still used by the caller */ if(post != null) { - if(transactionToken != null) { - post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, transactionToken); - } else { - post.remove(TransactionManager.TRANSACTION_TOKEN_PARAM); - } + if(transactionToken != null) { + post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, transactionToken); + } else { + post.remove(TransactionManager.TRANSACTION_TOKEN_PARAM); + } } - return apiurl; + return apiurl.toString(); } /** @@ -139,51 +139,50 @@ public class WorkTables extends Tables { * @param sb the {@link Switchboard} instance. Must not be null. * @return the most recently recorded call to the given API with the same parameters, or null when no one was found or data is not accessible */ - public static Row selectLastExecutedApiCall(final String servletName, final serverObjects post, final Switchboard sb) { - Row lastRecordedCall = null; - if (servletName != null && sb != null && sb.tables != null) { - try { - if (post != null && post.containsKey(WorkTables.TABLE_API_COL_APICALL_PK)) { - /* - * Search the table on the primary key when when present (re-execution of a - * recorded call) - */ - lastRecordedCall = sb.tables.select(WorkTables.TABLE_API_NAME, - UTF8.getBytes(post.get(WorkTables.TABLE_API_COL_APICALL_PK))); - } else { - /* Else search the table on the API URL as recorded (including parameters) */ - final String apiURL = WorkTables.generateRecordedURL(post, servletName); - final Iterator<Row> rowsIt = sb.tables.iterator(WorkTables.TABLE_API_NAME, - WorkTables.TABLE_API_COL_URL, UTF8.getBytes(apiURL)); - while (rowsIt.hasNext()) { - final Row currentRow = rowsIt.next(); - if (currentRow != null) { - final Date currentLastExec = currentRow.get(WorkTables.TABLE_API_COL_DATE_LAST_EXEC, - (Date) null); - if (currentLastExec != null) { - if (lastRecordedCall == null) { - /* - * Do not break now the loop : we are looking for the most recent API call on - * the same URL - */ - lastRecordedCall = currentRow; - } else if (lastRecordedCall.get(WorkTables.TABLE_API_COL_DATE_LAST_EXEC, (Date) null) - .before(currentLastExec)) { - lastRecordedCall = currentRow; - } - } - } - } - } - - } catch (final IOException e) { - ConcurrentLog.logException(e); - } catch (final SpaceExceededException e) { - ConcurrentLog.logException(e); - } - } - return lastRecordedCall; - } + public static Row selectLastExecutedApiCall(final String servletName, final serverObjects post, final Switchboard sb) { + Row lastRecordedCall = null; + if (servletName != null && sb != null && sb.tables != null) { + try { + if (post != null && post.containsKey(WorkTables.TABLE_API_COL_APICALL_PK)) { + /* + * Search the table on the primary key when when present (re-execution of a + * recorded call) + */ + lastRecordedCall = sb.tables.select(WorkTables.TABLE_API_NAME, post.getBytes(WorkTables.TABLE_API_COL_APICALL_PK)); + } else { + /* Else search the table on the API URL as recorded (including parameters) */ + final String apiURL = WorkTables.generateRecordedURL(post, servletName); + final Iterator<Row> rowsIt = sb.tables.iterator(WorkTables.TABLE_API_NAME, + WorkTables.TABLE_API_COL_URL, UTF8.getBytes(apiURL)); + while (rowsIt.hasNext()) { + final Row currentRow = rowsIt.next(); + if (currentRow != null) { + final Date currentLastExec = currentRow.get(WorkTables.TABLE_API_COL_DATE_LAST_EXEC, + (Date) null); + if (currentLastExec != null) { + if (lastRecordedCall == null) { + /* + * Do not break now the loop : we are looking for the most recent API call on + * the same URL + */ + lastRecordedCall = currentRow; + } else if (lastRecordedCall.get(WorkTables.TABLE_API_COL_DATE_LAST_EXEC, (Date) null) + .before(currentLastExec)) { + lastRecordedCall = currentRow; + } + } + } + } + } + + } catch (final IOException e) { + ConcurrentLog.logException(e); + } catch (final SpaceExceededException e) { + ConcurrentLog.logException(e); + } + } + return lastRecordedCall; + } /** * recording of a api call. stores the call parameters into the API database table @@ -264,7 +263,7 @@ public class WorkTables extends Tables { public byte[] recordAPICall(final serverObjects post, final String servletName, final String type, final String comment, int time, String unit) { if (post.containsKey(TABLE_API_COL_APICALL_PK)) { // this api call has already been stored somewhere. - return recordAPICall(post, servletName, type, comment); + return this.recordAPICall(post, servletName, type, comment); } if (time < 0 || unit == null || unit.isEmpty() || "minutes,hours,days".indexOf(unit) < 0) { time = 0; unit = ""; @@ -276,9 +275,9 @@ public class WorkTables extends Tables { /* Before API URL serialization, we set any eventual transaction token value to empty : * this will later help identify a new valid transaction token will be necessary, * but without revealing it in the URL displayed in the process scheduler and storing an invalid value */ - final String transactionToken = post.get(TransactionManager.TRANSACTION_TOKEN_PARAM); + final String transactionToken = post.get(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); if(transactionToken != null) { - post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); + post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, ""); } // generate the apicall url - without the apicall attributes @@ -286,9 +285,9 @@ public class WorkTables extends Tables { /* Now restore the eventual transaction token to prevent side effects on the post object eventually still used by the caller */ if(transactionToken != null) { - post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, transactionToken); + post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, transactionToken); } else { - post.remove(TransactionManager.TRANSACTION_TOKEN_PARAM); + post.remove(TransactionManager.TRANSACTION_TOKEN_PARAM); } byte[] pk = null; @@ -327,7 +326,7 @@ public class WorkTables extends Tables { * @return a map of the called urls and the http status code of the api call or -1 if any other IOException occurred */ public Map<String, Integer> execAPICalls(String host, int port, Collection<String> pks, final String username, final String pass) { - LinkedHashMap<String, Integer> l = new LinkedHashMap<String, Integer>(); + LinkedHashMap<String, Integer> l = new LinkedHashMap<>(); // now call the api URLs and store the result status try (final HTTPClient client = new HTTPClient(ClientIdentification.yacyInternetCrawlerAgent)) { client.setTimout(120000); @@ -335,7 +334,7 @@ public class WorkTables extends Tables { for (final String pk: pks) { row = null; try { - row = select(WorkTables.TABLE_API_NAME, UTF8.getBytes(pk)); + row = this.select(WorkTables.TABLE_API_NAME, UTF8.getBytes(pk)); } catch (final IOException e) { ConcurrentLog.logException(e); } catch (final SpaceExceededException e) { @@ -350,18 +349,18 @@ public class WorkTables extends Tables { // use 4 param MultiProtocolURL to allow api_row_url with searchpart (like url?p=a&p2=b ) in client.GETbytes() if (theapicall.length() > 1000 || isTokenProtectedAPI) { // use a POST to execute the call - execPostAPICall(host, port, username, pass, client, l, url, isTokenProtectedAPI); + this.execPostAPICall(host, port, username, pass, client, l, url, isTokenProtectedAPI); } else { // use a GET to execute the call ConcurrentLog.info("WorkTables", "executing url: " + url.toNormalform(true)); try { client.GETbytes(url, username, pass, false); // use GETbytes(MultiProtocolURL,..) form to allow url in parameter (&url=path% if(client.getStatusCode() == HttpStatus.SC_METHOD_NOT_ALLOWED) { - /* GET method not allowed (HTTP 450 status) : this may be an old API entry, - * now restricted to HTTP POST and requiring a transaction token. We try now with POST. */ - execPostAPICall(host, port, username, pass, client, l, url, true); + /* GET method not allowed (HTTP 450 status) : this may be an old API entry, + * now restricted to HTTP POST and requiring a transaction token. We try now with POST. */ + this.execPostAPICall(host, port, username, pass, client, l, url, true); } else { - l.put(url.toNormalform(true), client.getStatusCode()); + l.put(url.toNormalform(true), client.getStatusCode()); } } catch (final IOException e) { ConcurrentLog.logException(e); @@ -390,71 +389,71 @@ public class WorkTables extends Tables { * @param isTokenProtectedAPI set to true when the API is protected by a transaction token * @throws MalformedURLException when the HTTP POST url could not be derived from apiURL */ - private void execPostAPICall(String host, int port, final String username, final String pass, - final HTTPClient client, final LinkedHashMap<String, Integer> results, - final MultiProtocolURL apiURL, final boolean isTokenProtectedAPI) throws MalformedURLException { - Map<String, ContentBody> post = new HashMap<>(); - for (Map.Entry<String, String> a: apiURL.getAttributes().entrySet()) { - post.put(a.getKey(), UTF8.StringBody(a.getValue())); - } - - final MultiProtocolURL url = new MultiProtocolURL("http", host, port, apiURL.getPath()); - - try { - if (isTokenProtectedAPI) { - // Eventually acquire first a new valid transaction token before posting data - client.GETbytes(url, username, pass, false); - if (client.getStatusCode() != HttpStatus.SC_OK) { - /* Do not fail immediately, the token may be no more necessary on this API : - * let's log a warning but try anyway the POST call that will eventually reject the request */ - ConcurrentLog.warn("APICALL", "Could not retrieve a transaction token for " + apiURL.toNormalform(true)); - } else { - - final Header transactionTokenHeader = client.getHttpResponse() - .getFirstHeader(HeaderFramework.X_YACY_TRANSACTION_TOKEN); - if (transactionTokenHeader == null) { - /* - * Do not fail immediately, the token may be no more - * necessary on this API : let's log a warning but try - * anyway the POST call that will eventually reject the - * request - */ - ConcurrentLog.warn("APICALL", - "Could not retrieve a transaction token for " + apiURL.toNormalform(true)); - } else { - post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, - UTF8.StringBody(transactionTokenHeader.getValue())); - } - } - - } - - client.POSTbytes(url, "localhost", post, username, pass, false, false); - - results.put(apiURL.toNormalform(true), client.getStatusCode()); - } catch (final IOException e) { - ConcurrentLog.logException(e); - results.put(apiURL.toNormalform(true), -1); - } - } - - /** - * Executes an HTTP GET API call - * @param host target host name - * @param port target port - * @param path target path - * @param pk the primary key of the api call + private void execPostAPICall(String host, int port, final String username, final String pass, + final HTTPClient client, final LinkedHashMap<String, Integer> results, + final MultiProtocolURL apiURL, final boolean isTokenProtectedAPI) throws MalformedURLException { + Map<String, ContentBody> post = new HashMap<>(); + for (Map.Entry<String, String> a: apiURL.getAttributes().entrySet()) { + post.put(a.getKey(), UTF8.StringBody(a.getValue())); + } + + final MultiProtocolURL url = new MultiProtocolURL("http", host, port, apiURL.getPath()); + + try { + if (isTokenProtectedAPI) { + // Eventually acquire first a new valid transaction token before posting data + client.GETbytes(url, username, pass, false); + if (client.getStatusCode() != HttpStatus.SC_OK) { + /* Do not fail immediately, the token may be no more necessary on this API : + * let's log a warning but try anyway the POST call that will eventually reject the request */ + ConcurrentLog.warn("APICALL", "Could not retrieve a transaction token for " + apiURL.toNormalform(true)); + } else { + + final Header transactionTokenHeader = client.getHttpResponse() + .getFirstHeader(HeaderFramework.X_YACY_TRANSACTION_TOKEN); + if (transactionTokenHeader == null) { + /* + * Do not fail immediately, the token may be no more + * necessary on this API : let's log a warning but try + * anyway the POST call that will eventually reject the + * request + */ + ConcurrentLog.warn("APICALL", + "Could not retrieve a transaction token for " + apiURL.toNormalform(true)); + } else { + post.put(TransactionManager.TRANSACTION_TOKEN_PARAM, + UTF8.StringBody(transactionTokenHeader.getValue())); + } + } + + } + + client.POSTbytes(url, "localhost", post, username, pass, false, false); + + results.put(apiURL.toNormalform(true), client.getStatusCode()); + } catch (final IOException e) { + ConcurrentLog.logException(e); + results.put(apiURL.toNormalform(true), -1); + } + } + + /** + * Executes an HTTP GET API call + * @param host target host name + * @param port target port + * @param path target path + * @param pk the primary key of the api call * @param username authentication user name * @param pass authentication encoded password - * @return the API response HTTP status, or -1 when an error occured - */ + * @return the API response HTTP status, or -1 when an error occured + */ public static int execGetAPICall(String host, int port, String path, byte[] pk, final String username, final String pass) { // now call the api URLs and store the result status - String url = "http://" + host + ":" + port + path; - if (pk != null) url += "&" + WorkTables.TABLE_API_COL_APICALL_PK + "=" + UTF8.String(pk); + StringBuilder url = new StringBuilder("http://").append(host).append(":").append(port).append(path); + if (pk != null) url.append("&").append(WorkTables.TABLE_API_COL_APICALL_PK).append("=").append(UTF8.String(pk)); try (final HTTPClient client = new HTTPClient(ClientIdentification.yacyInternetCrawlerAgent)) { client.setTimout(120000); - client.GETbytes(url, username, pass, false); + client.GETbytes(url.toString(), username, pass, false); return client.getStatusCode(); } catch (final IOException e) { ConcurrentLog.logException(e); @@ -470,9 +469,9 @@ public class WorkTables extends Tables { * @return the http status code of the api call or -1 if any other IOException occurred */ public int execAPICall(String pk, String host, int port, final String username, final String pass) { - ArrayList<String> pks = new ArrayList<String>(); + ArrayList<String> pks = new ArrayList<>(); pks.add(pk); - Map<String, Integer> m = execAPICalls(host, port, pks, username, pass); + Map<String, Integer> m = this.execAPICalls(host, port, pks, username, pass); if (m.isEmpty()) return -1; return m.values().iterator().next().intValue(); } @@ -533,7 +532,7 @@ public class WorkTables extends Tables { } public static Map<byte[], String> commentCache(Switchboard sb) { - Map<byte[], String> comments = new TreeMap<byte[], String>(Base64Order.enhancedCoder); + Map<byte[], String> comments = new TreeMap<>(Base64Order.enhancedCoder); Iterator<Tables.Row> i; try { i = sb.tables.iterator(WorkTables.TABLE_API_NAME); diff --git a/source/net/yacy/document/importer/WarcImporter.java b/source/net/yacy/document/importer/WarcImporter.java index 02787e4be..2b8bdbd05 100644 --- a/source/net/yacy/document/importer/WarcImporter.java +++ b/source/net/yacy/document/importer/WarcImporter.java @@ -111,6 +111,20 @@ public class WarcImporter extends Thread implements Importer { this.collection = collection;
}
+ public WarcImporter(File f, InputStream is, String collection) throws IOException {
+ super("WarcImporter - from file " + f.getName());
+ this.name = f.getName();
+ if (!f.exists() && is != null) {
+ this.sourceSize = is.available();
+ this.source = is;
+ } else {
+ this.sourceSize = f.length();
+ this.source = new FileInputStream(f);
+ if (this.name.endsWith(".gz")) this.source = new GZIPInputStream(this.source);
+ }
+ this.collection = collection;
+ }
+
/**
* Reads a Warc file and adds all contained responses to the index.
* The reader automatically handles plain or gzip'd warc files
diff --git a/source/net/yacy/document/importer/ZimImporter.java b/source/net/yacy/document/importer/ZimImporter.java index a1c002016..6d0fcc8c3 100644 --- a/source/net/yacy/document/importer/ZimImporter.java +++ b/source/net/yacy/document/importer/ZimImporter.java @@ -25,6 +25,7 @@ package net.yacy.document.importer; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -75,7 +76,6 @@ public class ZimImporter extends Thread implements Importer { private ZIMReader reader; private final String path; private String guessedSource; - private final byte[] data; private final String collection; private int recordCnt; @@ -87,11 +87,11 @@ public class ZimImporter extends Thread implements Importer { public ZimImporter(String path, byte[] data, String collection) throws IOException { super("ZimImporter - from file " + path); this.path = path; - this.data = data; File zimFilePath = new File(path); - if (!zimFilePath.exists() && this.data != null) { + if (!zimFilePath.exists() && data != null) { File tempFile = File.createTempFile(zimFilePath.getName().substring(0, zimFilePath.getName().length() - 4), "zim"); FileUtils.writeByteArrayToFile(tempFile, data); + tempFile.deleteOnExit(); this.file = new ZIMFile(tempFile.getPath()); } else { this.file = new ZIMFile(this.path); // this will read already some of the metadata and could consume some time @@ -100,6 +100,22 @@ public class ZimImporter extends Thread implements Importer { this.collection = collection; } + public ZimImporter(String path, InputStream is, String collection) throws IOException { + super("ZimImporter - from file " + path); + this.path = path; + File zimFilePath = new File(path); + if (!zimFilePath.exists() && is != null) { + File tempFile = File.createTempFile(zimFilePath.getName().substring(0, zimFilePath.getName().length() - 4), "zim"); + FileUtils.copyInputStreamToFile(is, tempFile); + tempFile.deleteOnExit(); + this.file = new ZIMFile(tempFile.getPath()); + } else { + this.file = new ZIMFile(this.path); // this will read already some of the metadata and could consume some time + } + this.sourceSize = this.file.length(); + this.collection = collection; + } + @Override public void run() { job = this; diff --git a/source/net/yacy/htroot/Blog.java b/source/net/yacy/htroot/Blog.java index 01d3f49f4..d0067b298 100644 --- a/source/net/yacy/htroot/Blog.java +++ b/source/net/yacy/htroot/Blog.java @@ -163,7 +163,7 @@ public class Blog { // create a news message if (!sb.isRobinsonMode()) { - final Map<String, String> map = new HashMap<String, String>(); + final Map<String, String> map = new HashMap<>(); map.put("page", pagename); map.put("subject", StrSubject.replace(',', ' ')); map.put("author", strAuthor.replace(',', ' ')); @@ -217,7 +217,7 @@ public class Blog { } else if (post.containsKey("xmlfile")) { prop.put("mode", "5"); - if(sb.blogDB.importXML(post.get("xmlfile$file"))) { + if (sb.blogDB.importXML(post.getInputStream("xmlfile$file"))) { prop.put("mode_state", "1"); } else { diff --git a/source/net/yacy/htroot/CrawlResults.java b/source/net/yacy/htroot/CrawlResults.java index cf680f1fa..3b7adbcb0 100644 --- a/source/net/yacy/htroot/CrawlResults.java +++ b/source/net/yacy/htroot/CrawlResults.java @@ -133,16 +133,16 @@ public class CrawlResults { if (post.containsKey("clearlist")) ResultURLs.clearStack(tabletype);
if (post.containsKey("deleteentry")) {
- final String hash = post.get("hash", null);
+ final byte[] hash = post.getBytes("hash");
if (hash != null) {
// delete from database
- sb.index.fulltext().remove(hash.getBytes());
+ sb.index.fulltext().remove(hash);
}
}
if (post.containsKey("deletedomain") || post.containsKey("delandaddtoblacklist")) {
- final String domain = post.get("domain", null);
- if (domain != null) {
+ final String domain = post.get("domain", "");
+ if (domain != null && domain.length() > 0) {
selectedblacklist = post.get("blacklistname");
final Set<String> hostnames = new HashSet<>();
hostnames.add(domain);
diff --git a/source/net/yacy/htroot/IndexCreateParserErrors_p.java b/source/net/yacy/htroot/IndexCreateParserErrors_p.java index 0840be2e0..0bec8313f 100644 --- a/source/net/yacy/htroot/IndexCreateParserErrors_p.java +++ b/source/net/yacy/htroot/IndexCreateParserErrors_p.java @@ -89,6 +89,8 @@ public class IndexCreateParserErrors_p { if (cause == null) {
// well that should not really happen but occurs in a specific combination of running crawls for same domains with different url filters
prop.put("rejected_list_"+j+"_failreason", "no fail reason given");
+ } else if (cause.contains(CrawlStacker.ERROR_REDIRECT)) {
+ continue; // do not show redirects in this list
} else if (cause.startsWith(CrawlStacker.ERROR_NO_MATCH_MUST_MATCH_FILTER)) {
prop.put("rejected_list_"+j+"_failreason", "(<a href=\"/RegexTest.html?text=" + url.toNormalform(false) +
"®ex=" + cause.substring(CrawlStacker.ERROR_NO_MATCH_MUST_MATCH_FILTER.length()) + "\">test</a>) " + cause);
diff --git a/source/net/yacy/htroot/IndexImportWarc_p.java b/source/net/yacy/htroot/IndexImportWarc_p.java index 28e9e3957..58fb62818 100644 --- a/source/net/yacy/htroot/IndexImportWarc_p.java +++ b/source/net/yacy/htroot/IndexImportWarc_p.java @@ -20,10 +20,10 @@ package net.yacy.htroot; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import net.yacy.cora.document.id.MultiProtocolURL; -import net.yacy.cora.order.Base64Order; import net.yacy.cora.protocol.RequestHeader; import net.yacy.document.importer.WarcImporter; import net.yacy.server.serverObjects; @@ -54,13 +54,12 @@ public class IndexImportWarc_p { if (post.containsKey("file") || post.containsKey("url")) { final String filename = post.get("file"); final String collection = post.get("collection", "user"); - final String data64 = post.get("file$file", null); // file uploads are all base64-encoded in YaCyDefaultServlet.parseMultipart - final byte[] data = data64 == null ? null : Base64Order.standardCoder.decode(data64); + final InputStream is = post.getInputStream("file$file"); if (filename != null && filename.length() > 0) { final File sourcefile = new File(filename); - if (sourcefile.exists() || data != null) { + if (sourcefile.exists() || is != null) { try { - final WarcImporter wi = new WarcImporter(sourcefile, data, collection); + final WarcImporter wi = new WarcImporter(sourcefile, is, collection); wi.start(); prop.put("import_thread", "started"); } catch (final IOException ex) { diff --git a/source/net/yacy/htroot/IndexImportZim_p.java b/source/net/yacy/htroot/IndexImportZim_p.java index 07b9c2039..f96b3639a 100644 --- a/source/net/yacy/htroot/IndexImportZim_p.java +++ b/source/net/yacy/htroot/IndexImportZim_p.java @@ -20,8 +20,8 @@ package net.yacy.htroot; import java.io.File; import java.io.IOException; +import java.io.InputStream; -import net.yacy.cora.order.Base64Order; import net.yacy.cora.protocol.RequestHeader; import net.yacy.document.importer.ZimImporter; import net.yacy.server.serverObjects; @@ -56,13 +56,12 @@ public class IndexImportZim_p { if (post.containsKey("file")) { final String filename = post.get("file"); final String collection = post.get("collection", "user"); - final String data64 = post.get("file$file", null); // file uploads are all base64-encoded in YaCyDefaultServlet.parseMultipart - final byte[] data = data64 == null ? null : Base64Order.standardCoder.decode(data64); + final InputStream is = post.getInputStream("file$file"); if (filename != null && filename.length() > 0) { final File sourcefile = new File(filename); - if (data != null || sourcefile.exists()) { + if (is != null || sourcefile.exists()) { try { - final ZimImporter zi = new ZimImporter(sourcefile.getAbsolutePath(), data, collection); + final ZimImporter zi = new ZimImporter(sourcefile.getAbsolutePath(), is, collection); zi.start(); prop.put("import_thread", "started"); } catch (final IOException ex) { diff --git a/source/net/yacy/htroot/Load_RSS_p.java b/source/net/yacy/htroot/Load_RSS_p.java index 565b26dc3..e912d3ac5 100644 --- a/source/net/yacy/htroot/Load_RSS_p.java +++ b/source/net/yacy/htroot/Load_RSS_p.java @@ -99,7 +99,7 @@ public class Load_RSS_p { final Iterator<Row> plainIterator = sb.tables.iterator("rss"); Row row; String messageurl; - final List<byte[]> d = new ArrayList<byte[]>(); + final List<byte[]> d = new ArrayList<>(); while (plainIterator.hasNext()) { row = plainIterator.next(); if (row == null) continue; @@ -141,7 +141,7 @@ public class Load_RSS_p { final Iterator<Row> plainIterator = sb.tables.iterator("rss"); Row row; String messageurl; - final List<byte[]> d = new ArrayList<byte[]>(); + final List<byte[]> d = new ArrayList<>(); while (plainIterator.hasNext()) { row = plainIterator.next(); if (row == null) continue; @@ -295,7 +295,7 @@ public class Load_RSS_p { // index all selected items: description only if (rss != null && post.containsKey("indexSelectedItemContent")) { final RSSFeed feed = rss.getFeed(); - final Map<String, DigestURL> hash2UrlMap = new HashMap<String, DigestURL>(); + final Map<String, DigestURL> hash2UrlMap = new HashMap<>(); loop: for (final Map.Entry<String, String> entry: post.entrySet()) { if (entry.getValue().startsWith(CHECKBOX_ITEM_PREFIX)) { /* Process selected item links */ @@ -336,7 +336,7 @@ public class Load_RSS_p { } } - final List<DigestURL> urlsToIndex = new ArrayList<DigestURL>(); + final List<DigestURL> urlsToIndex = new ArrayList<>(); loop: for (final Map.Entry<String, DigestURL> entry: hash2UrlMap.entrySet()) { final DigestURL messageUrl = entry.getValue(); final HarvestProcess harvestProcess = sb.getHarvestProcess(ASCII.String(messageUrl.hash())); @@ -358,7 +358,7 @@ public class Load_RSS_p { if (record_api && rss != null && rss.getFeed() != null && rss.getFeed().getChannel() != null) { // record API action - RSSLoader.recordAPI(sb, post.get(WorkTables.TABLE_API_COL_APICALL_PK, null), url, rss.getFeed(), repeat_time, repeat_unit); + RSSLoader.recordAPI(sb, post.get(WorkTables.TABLE_API_COL_APICALL_PK, ""), url, rss.getFeed(), repeat_time, repeat_unit); } // show items from rss diff --git a/source/net/yacy/htroot/Network.java b/source/net/yacy/htroot/Network.java index b8cc05390..a2cb5af59 100644 --- a/source/net/yacy/htroot/Network.java +++ b/source/net/yacy/htroot/Network.java @@ -332,11 +332,11 @@ public class Network { final long onlySizeLessDocs = post == null ? Long.MAX_VALUE : post.getLong("onlysizelessdocs", Long.MAX_VALUE);
Iterator<Seed> e = null;
final boolean order = (post != null && post.get("order", "down").equals("up"));
- final String sort = (post == null ? null : post.get("sort", null));
+ final String sort = (post == null ? null : post.get("sort", ""));
switch (page) {
- case 1 : e = sb.peers.seedsSortedConnected(order, (sort == null ? Seed.LCOUNT : sort)); break;
- case 2 : e = sb.peers.seedsSortedDisconnected(order, (sort == null ? Seed.LASTSEEN : sort)); break;
- case 3 : e = sb.peers.seedsSortedPotential(order, (sort == null ? Seed.LASTSEEN : sort)); break;
+ case 1 : e = sb.peers.seedsSortedConnected(order, (sort.length() == 0 ? Seed.LCOUNT : sort)); break;
+ case 2 : e = sb.peers.seedsSortedDisconnected(order, (sort.length() == 0 ? Seed.LASTSEEN : sort)); break;
+ case 3 : e = sb.peers.seedsSortedPotential(order, (sort.length() == 0 ? Seed.LASTSEEN : sort)); break;
default: break;
}
String startURL;
diff --git a/source/net/yacy/htroot/QuickCrawlLink_p.java b/source/net/yacy/htroot/QuickCrawlLink_p.java index 6c2f432f4..3f12dd486 100644 --- a/source/net/yacy/htroot/QuickCrawlLink_p.java +++ b/source/net/yacy/htroot/QuickCrawlLink_p.java @@ -84,9 +84,9 @@ public class QuickCrawlLink_p { }
// get the URL
- String crawlingStart = post.get("url",null);
+ String crawlingStart = post.get("url", "");
- if (crawlingStart != null) {
+ if (crawlingStart.length() == 0) {
prop.put("mode", "1");
crawlingStart = UTF8.decodeURL(crawlingStart);
@@ -94,8 +94,9 @@ public class QuickCrawlLink_p { final Segment indexSegment = sb.index;
// get the browser title
- String title = post.get("title", null);
- if(title != null) {
+ String title = post.get("title", "");
+ if (title.length() == 0) { + title = crawlingStart;
/* Decode eventual special(non ASCII) characters in title */
title = UTF8.decodeURL(title);
}
@@ -175,7 +176,7 @@ public class QuickCrawlLink_p { }
// stack URL
- String reasonString = null;
+ String reasonString;
reasonString = sb.crawlStacker.stackCrawl(new Request(
sb.peers.mySeed().hash.getBytes(),
crawlingStartURL,
diff --git a/source/net/yacy/htroot/Supporter.java b/source/net/yacy/htroot/Supporter.java index ca05c1e01..297d2440b 100644 --- a/source/net/yacy/htroot/Supporter.java +++ b/source/net/yacy/htroot/Supporter.java @@ -73,27 +73,27 @@ public class Supporter { // read voting String hash; - if ((post != null) && ((hash = post.get("voteNegative", null)) != null)) { + if ((post != null) && ((hash = post.get("voteNegative", "")).length() > 0)) { if (!sb.verifyAuthentication(header)) { prop.authenticationRequired(); return prop; } // make new news message with voting if (!sb.isRobinsonMode()) { - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("urlhash", hash); map.put("vote", "negative"); map.put("refid", post.get("refid", "")); sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_SURFTIPP_VOTE_ADD, map); } } - if ((post != null) && ((hash = post.get("votePositive", null)) != null)) { + if ((post != null) && ((hash = post.get("votePositive", "")).length() > 0)) { if (!sb.verifyAuthentication(header)) { prop.authenticationRequired(); return prop; } // make new news message with voting - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("urlhash", hash); map.put("url", crypt.simpleDecode(post.get("url", ""))); map.put("title", crypt.simpleDecode(post.get("title", ""))); @@ -105,14 +105,14 @@ public class Supporter { } // create Supporter - final HashMap<String, Integer> negativeHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) - final HashMap<String, Integer> positiveHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> negativeHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> positiveHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); //accumulateVotes(negativeHashes, positiveHashes, yacyNewsPool.OUTGOING_DB); //accumulateVotes(negativeHashes, positiveHashes, yacyNewsPool.PUBLISHED_DB); - final ScoreMap<String> ranking = new ConcurrentScoreMap<String>(); // score cluster for url hashes + final ScoreMap<String> ranking = new ConcurrentScoreMap<>(); // score cluster for url hashes final Row rowdef = new Row("String url-255, String title-120, String description-120, String refid-" + (GenericFormatter.PATTERN_SHORT_SECOND.length() + 12), NaturalOrder.naturalOrder); - final HashMap<String, Entry> Supporter = new HashMap<String, Entry>(); // a mapping from an url hash to a kelondroRow.Entry with display properties + final HashMap<String, Entry> Supporter = new HashMap<>(); // a mapping from an url hash to a kelondroRow.Entry with display properties accumulateSupporter(sb, Supporter, ranking, rowdef, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); //accumulateSupporter(Supporter, ranking, rowdef, negativeHashes, positiveHashes, yacyNewsPool.OUTGOING_DB); //accumulateSupporter(Supporter, ranking, rowdef, negativeHashes, positiveHashes, yacyNewsPool.PUBLISHED_DB); diff --git a/source/net/yacy/htroot/Surftips.java b/source/net/yacy/htroot/Surftips.java index 47e220b38..0f21dcd93 100644 --- a/source/net/yacy/htroot/Surftips.java +++ b/source/net/yacy/htroot/Surftips.java @@ -81,27 +81,27 @@ public class Surftips { // read voting String hash; - if ((post != null) && ((hash = post.get("voteNegative", null)) != null)) { + if ((post != null) && ((hash = post.get("voteNegative", "")).length() > 0)) { if (!sb.verifyAuthentication(header)) { prop.authenticationRequired(); return prop; } // make new news message with voting if (sb.isRobinsonMode()) { - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("urlhash", hash); map.put("vote", "negative"); map.put("refid", post.get("refid", "")); sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_SURFTIPP_VOTE_ADD, map); } } - if ((post != null) && ((hash = post.get("votePositive", null)) != null)) { + if ((post != null) && ((hash = post.get("votePositive", "")).length() > 0)) { if (!sb.verifyAuthentication(header)) { prop.authenticationRequired(); return prop; } // make new news message with voting - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("urlhash", hash); map.put("url", crypt.simpleDecode(post.get("url", ""))); map.put("title", crypt.simpleDecode(post.get("title", ""))); @@ -113,14 +113,14 @@ public class Surftips { } // create surftips - final HashMap<String, Integer> negativeHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) - final HashMap<String, Integer> positiveHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> negativeHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> positiveHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) accumulateVotes(sb , negativeHashes, positiveHashes, NewsPool.INCOMING_DB); //accumulateVotes(negativeHashes, positiveHashes, yacyNewsPool.OUTGOING_DB); //accumulateVotes(negativeHashes, positiveHashes, yacyNewsPool.PUBLISHED_DB); - final ScoreMap<String> ranking = new ConcurrentScoreMap<String>(); // score cluster for url hashes + final ScoreMap<String> ranking = new ConcurrentScoreMap<>(); // score cluster for url hashes final Row rowdef = new Row("String url-255, String title-120, String description-120, String refid-" + (GenericFormatter.PATTERN_SHORT_SECOND.length() + 12), NaturalOrder.naturalOrder); - final HashMap<String, Entry> surftips = new HashMap<String, Entry>(); // a mapping from an url hash to a kelondroRow.Entry with display properties + final HashMap<String, Entry> surftips = new HashMap<>(); // a mapping from an url hash to a kelondroRow.Entry with display properties accumulateSurftips(sb, surftips, ranking, rowdef, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); //accumulateSurftips(surftips, ranking, rowdef, negativeHashes, positiveHashes, yacyNewsPool.OUTGOING_DB); //accumulateSurftips(surftips, ranking, rowdef, negativeHashes, positiveHashes, yacyNewsPool.PUBLISHED_DB); diff --git a/source/net/yacy/htroot/Tables_p.java b/source/net/yacy/htroot/Tables_p.java index c9a11016f..09633bf90 100644 --- a/source/net/yacy/htroot/Tables_p.java +++ b/source/net/yacy/htroot/Tables_p.java @@ -51,8 +51,8 @@ public class Tables_p { prop.put("showedit", 0);
prop.put("showselection", 0);
- String table = (post == null) ? null : post.get("table", null);
- if (table != null && !sb.tables.hasHeap(table)) table = null;
+ String table = (post == null) ? null : post.get("table", "");
+ if (table != null && table.length() > 0 && !sb.tables.hasHeap(table)) table = null;
// show table selection
int count = 0;
@@ -62,7 +62,7 @@ public class Tables_p { while (ti.hasNext()) {
tablename = ti.next();
prop.put("showselection_tables_" + count + "_name", tablename);
- prop.put("showselection_tables_" + count + "_selected", (table != null && table.equals(tablename)) ? 1 : 0);
+ prop.put("showselection_tables_" + count + "_selected", (table != null && table.length() > 0 && table.equals(tablename)) ? 1 : 0);
count++;
}
prop.put("showselection_tables", count);
@@ -71,8 +71,8 @@ public class Tables_p { if (post == null) return prop; // return rewrite properties
- final String counts = post.get("count", null);
- int maxcount = (counts == null || counts.equals("all")) ? Integer.MAX_VALUE : post.getInt("count", 10);
+ final String counts = post.get("count", "");
+ int maxcount = (counts == null || counts.length() == 0 || counts.equals("all")) ? Integer.MAX_VALUE : post.getInt("count", 10);
final boolean reverse = post.containsKey("reverse") ? post.getBoolean("reverse") : false;
prop.put("showselection_reverse", reverse ? 1 : 0);
final String pattern = post.get("search", "");
@@ -95,7 +95,7 @@ public class Tables_p { if (post.get("commitrow", "").length() > 0) {
final String pk = post.get("pk");
- final Map<String, byte[]> map = new HashMap<String, byte[]>();
+ final Map<String, byte[]> map = new HashMap<>();
for (final Map.Entry<String, String> entry: post.entrySet()) {
if (entry.getKey().startsWith("col_")) {
map.put(entry.getKey().substring(4), entry.getValue().getBytes());
@@ -120,7 +120,7 @@ public class Tables_p { columns = sb.tables.columns(table);
} catch (final IOException e) {
ConcurrentLog.logException(e);
- columns = new ArrayList<String>();
+ columns = new ArrayList<>();
}
if (post.containsKey("editrow")) {
diff --git a/source/net/yacy/htroot/TransNews_p.java b/source/net/yacy/htroot/TransNews_p.java index c0652d855..5883bec07 100644 --- a/source/net/yacy/htroot/TransNews_p.java +++ b/source/net/yacy/htroot/TransNews_p.java @@ -106,7 +106,7 @@ public class TransNews_p { } } if (sendit) { - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("language", currentlang); map.put("file", file); map.put("source", sourcetxt); @@ -119,11 +119,11 @@ public class TransNews_p { } } String refid; - if ((post != null) && ((refid = post.get("voteNegative", null)) != null)) { + if ((post != null) && ((refid = post.get("voteNegative", "")).length() > 0)) { // make new news message with voting if (!sb.isRobinsonMode()) { - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("language", currentlang); map.put("file", crypt.simpleDecode(post.get("filename", ""))); map.put("source", crypt.simpleDecode(post.get("source", ""))); @@ -138,7 +138,7 @@ public class TransNews_p { } } - if ((post != null) && ((refid = post.get("votePositive", null)) != null)) { + if ((post != null) && ((refid = post.get("votePositive", "")).length() > 0)) { final String filename = post.get("filename"); @@ -152,7 +152,7 @@ public class TransNews_p { } // TODO: shall we post voting if translation is not new ? // make new news message with voting - final HashMap<String, String> map = new HashMap<String, String>(); + final HashMap<String, String> map = new HashMap<>(); map.put("language", currentlang); map.put("file", crypt.simpleDecode(filename)); map.put("source", crypt.simpleDecode(post.get("source", ""))); @@ -168,11 +168,11 @@ public class TransNews_p { } // create Translation voting list - final HashMap<String, Integer> negativeHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) - final HashMap<String, Integer> positiveHashes = new HashMap<String, Integer>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> negativeHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) + final HashMap<String, Integer> positiveHashes = new HashMap<>(); // a mapping from an url hash to Integer (count of votes) accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); - final ScoreMap<String> ranking = new ConcurrentScoreMap<String>(); // score cluster for url hashes - final HashMap<String, NewsDB.Record> translation = new HashMap<String, NewsDB.Record>(); // a mapping from an url hash to a kelondroRow.Entry with display properties + final ScoreMap<String> ranking = new ConcurrentScoreMap<>(); // score cluster for url hashes + final HashMap<String, NewsDB.Record> translation = new HashMap<>(); // a mapping from an url hash to a kelondroRow.Entry with display properties accumulateTranslations(sb, translation, ranking, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); // read out translation-news array and create property entries diff --git a/source/net/yacy/htroot/ViewFile.java b/source/net/yacy/htroot/ViewFile.java index d94943ed6..25d9d0072 100644 --- a/source/net/yacy/htroot/ViewFile.java +++ b/source/net/yacy/htroot/ViewFile.java @@ -211,7 +211,7 @@ public class ViewFile { return prop;
}
- final String[] wordArray = wordArray(post.get("words", null));
+ final String[] wordArray = wordArray(post.get("words", ""));
if (viewMode.equals("iframeWeb")) {
prop.put("viewMode", VIEW_MODE_AS_IFRAME_FROM_WEB);
prop.put("viewMode_url", url.toNormalform(true));
diff --git a/source/net/yacy/htroot/Vocabulary_p.java b/source/net/yacy/htroot/Vocabulary_p.java index ec4729798..cd22dba65 100644 --- a/source/net/yacy/htroot/Vocabulary_p.java +++ b/source/net/yacy/htroot/Vocabulary_p.java @@ -83,9 +83,9 @@ public class Vocabulary_p { final Collection<Tagging> vocs = LibraryProvider.autotagging.getVocabularies(); - String vocabularyName = (post == null) ? null : post.get("vocabulary", null); - final String discovername = (post == null) ? null : post.get("discovername", null); - Tagging vocabulary = vocabularyName == null ? null : LibraryProvider.autotagging.getVocabulary(vocabularyName); + String vocabularyName = (post == null) ? null : post.get("vocabulary", ""); + final String discovername = (post == null) ? null : post.get("discovername", ""); + Tagging vocabulary = vocabularyName == null || vocabularyName.length() == 0 ? null : LibraryProvider.autotagging.getVocabulary(vocabularyName); if (vocabulary == null) { vocabularyName = null; } @@ -100,7 +100,7 @@ public class Vocabulary_p { MultiProtocolURL discoveruri = null; if (discoverobjectspace.length() > 0) try {discoveruri = new MultiProtocolURL(discoverobjectspace);} catch (final MalformedURLException e) {} if (discoveruri == null) discoverobjectspace = ""; - final Map<String, Tagging.SOTuple> table = new LinkedHashMap<String, Tagging.SOTuple>(); + final Map<String, Tagging.SOTuple> table = new LinkedHashMap<>(); final File propFile = LibraryProvider.autotagging.getVocabularyFile(discovername); final boolean discoverNot = post.get("discovermethod", "").equals("none"); final boolean discoverFromPath = post.get("discovermethod", "").equals("path"); diff --git a/source/net/yacy/htroot/WebStructurePicture_p.java b/source/net/yacy/htroot/WebStructurePicture_p.java index 7e5ea9e38..8aeac3c27 100644 --- a/source/net/yacy/htroot/WebStructurePicture_p.java +++ b/source/net/yacy/htroot/WebStructurePicture_p.java @@ -81,7 +81,7 @@ public class WebStructurePicture_p { nodes = post.getInt("nodes", width * height * 100 / 1024 / 576); bf = post.getInt("bf", depth <= 0 ? -1 : (int) Math.round(2.0d * Math.pow(nodes, 1.0d / depth))); time = post.getInt("time", -1); - hosts = post.get("host", null); + hosts = post.get("host", ""); color_text = post.get("colortext", color_text); color_back = post.get("colorback", color_back); color_dot0 = post.get("colordot0", color_dot0); @@ -100,7 +100,7 @@ public class WebStructurePicture_p { hosts = sb.webStructure.hostWithMaxReferences(); } final RasterPlotter graphPicture; - if (hosts == null) { + if (hosts == null || hosts.isEmpty()) { // probably no information available final RasterPlotter.DrawMode drawMode = (RasterPlotter.darkColor(color_back)) ? RasterPlotter.DrawMode.MODE_ADD : RasterPlotter.DrawMode.MODE_SUB; graphPicture = new RasterPlotter(width, height, drawMode, color_back); @@ -150,10 +150,10 @@ public class WebStructurePicture_p { nextlayer++; final double radius = 1.0 / (1 << nextlayer); final Map<String, Integer> next = structure.outgoingReferencesByHostName(hostName); - final ClusteredScoreMap<String> next0 = new ClusteredScoreMap<String>(false); + final ClusteredScoreMap<String> next0 = new ClusteredScoreMap<>(false); for (final Map.Entry<String, Integer> entry: next.entrySet()) next0.set(entry.getKey(), entry.getValue()); // first set points to next hosts - final Set<String> targetHostNames = new HashSet<String>(); + final Set<String> targetHostNames = new HashSet<>(); int maxtargetrefs = 8, maxthisrefs = 8; int targetrefs, thisrefs; double rr, re; diff --git a/source/net/yacy/htroot/Wiki.java b/source/net/yacy/htroot/Wiki.java index f4a137b26..22d01e274 100644 --- a/source/net/yacy/htroot/Wiki.java +++ b/source/net/yacy/htroot/Wiki.java @@ -117,7 +117,7 @@ public class Wiki { final WikiBoard.Entry newEntry = sb.wikiDB.newEntry(pagename, author, ip, post.get("reason", "edit"), content);
sb.wikiDB.write(newEntry);
// create a news message
- final Map<String, String> map = new HashMap<String, String>();
+ final Map<String, String> map = new HashMap<>();
map.put("page", pagename);
map.put("author", author.replace(',', ' '));
if (!sb.isRobinsonMode() && post.get("content", "").trim().length() > 0 && !Arrays.equals(page.page(), content)) {
@@ -192,11 +192,11 @@ public class Wiki { entry = sb.wikiDB.readBkp(UTF8.String(it.next()));
prop.put("mode_error_versions_" + count + "_date", WikiBoard.dateString(entry.date()));
prop.put("mode_error_versions_" + count + "_fdate", dateString(entry.date()));
- if (WikiBoard.dateString(entry.date()).equals(post.get("old", null))) {
+ if (WikiBoard.dateString(entry.date()).equals(post.get("old", ""))) {
prop.put("mode_error_versions_" + count + "_oldselected", "1");
oentry = entry;
oldselected = true;
- } else if (WikiBoard.dateString(entry.date()).equals(post.get("new", null))) {
+ } else if (WikiBoard.dateString(entry.date()).equals(post.get("new", ""))) {
prop.put("mode_error_versions_" + count + "_newselected", "1");
nentry = entry;
newselected = true;
diff --git a/source/net/yacy/htroot/api/linkstructure.java b/source/net/yacy/htroot/api/linkstructure.java index e2c69517b..cf41c4f65 100644 --- a/source/net/yacy/htroot/api/linkstructure.java +++ b/source/net/yacy/htroot/api/linkstructure.java @@ -56,9 +56,9 @@ public class linkstructure { final HyperlinkGraph hlg = new HyperlinkGraph(); int maxdepth = 0; - if (post.get("about", null) != null) try { + if (post.get("about", "").length() > 0) try { // get link structure within a host - final String about = post.get("about", null); // may be a URL, a URL hash or a domain hash + final String about = post.get("about", ""); // may be a URL, a URL hash or a domain hash DigestURL url = null; String hostname = null; if (about.length() == 12 && Base64Order.enhancedCoder.wellformed(ASCII.getBytes(about))) { @@ -79,10 +79,10 @@ public class linkstructure { hlg.fill(fulltext.getDefaultConnector(), hostname, null, maxtime, maxnodes); maxdepth = hlg.findLinkDepth(); } catch (final MalformedURLException e) {} - else if (post.get("to", null) != null) try { + else if (post.get("to", "").length() > 0) try { // get link structure between two links - final DigestURL to = new DigestURL(post.get("to", null), null); // must be an url - final DigestURL from = post.get("from", null) == null ? null : new DigestURL(post.get("from", null)); // can be null or must be an url + final DigestURL to = new DigestURL(post.get("to", ""), null); // must be an url + final DigestURL from = post.get("from", "").length() == 0 ? null : new DigestURL(post.get("from", "")); // can be null or must be an url hlg.path(sb.index, from, to, maxtime, maxnodes); } catch (final MalformedURLException e) {} diff --git a/source/net/yacy/htroot/api/push_p.java b/source/net/yacy/htroot/api/push_p.java index 02ff14931..203bff7ac 100644 --- a/source/net/yacy/htroot/api/push_p.java +++ b/source/net/yacy/htroot/api/push_p.java @@ -24,9 +24,7 @@ import java.net.MalformedURLException; import java.util.Date; import net.yacy.cora.document.encoding.ASCII; -import net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.document.id.DigestURL; -import net.yacy.cora.order.Base64Order; import net.yacy.cora.protocol.HeaderFramework; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.protocol.ResponseHeader; @@ -75,9 +73,7 @@ public class push_p { final String collection = post.get("collection-" + i, ""); final String lastModified = post.get("lastModified-" + i, ""); // must be in RFC1123 format final String contentType = post.get("contentType-" + i, ""); - final String data64 = post.get("data-" + i + "$file", ""); // multi-file uploads are all base64-encoded in YaCyDefaultServlet.parseMultipart - byte[] data = Base64Order.standardCoder.decode(data64); - if ((data == null || data.length == 0) && data64.length() > 0) data = UTF8.getBytes(data64); // for test cases + final byte[] data = post.getBytes("data-" + i + "$file"); // create response header final ResponseHeader responseHeader = new ResponseHeader(200); diff --git a/source/net/yacy/htroot/api/share.java b/source/net/yacy/htroot/api/share.java index 104827438..40775f045 100644 --- a/source/net/yacy/htroot/api/share.java +++ b/source/net/yacy/htroot/api/share.java @@ -27,8 +27,6 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import net.yacy.yacy; -import net.yacy.cora.document.encoding.UTF8; -import net.yacy.cora.order.Base64Order; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.util.ConcurrentLog; import net.yacy.search.SwitchboardConstants; @@ -83,15 +81,7 @@ public class share { } // check data - final String dataString = post.get("data$file", ""); - if (dataString.length() == 0) return prop; - byte[] data; - if (filename.endsWith(".base64")) { - data = Base64Order.standardCoder.decode(dataString); - filename = filename.substring(0, filename.length() - 7); - } else { - data = UTF8.getBytes(dataString); - } + final byte[] data = post.getBytes("data$file"); if (data == null || data.length == 0) return prop; // modify the file name; ignore and replace the used transaction token diff --git a/source/net/yacy/htroot/api/table_p.java b/source/net/yacy/htroot/api/table_p.java index bca9e37ec..506dedb8a 100644 --- a/source/net/yacy/htroot/api/table_p.java +++ b/source/net/yacy/htroot/api/table_p.java @@ -84,8 +84,8 @@ public class table_p { final String selectKey = post.containsKey("selectKey") ? post.get("selectKey") : null;
final String selectValue = (selectKey != null && post.containsKey("selectValue")) ? post.get("selectValue") : null;
- final String counts = post.get("count", null);
- int maxcount = (counts == null || counts.equals("all")) ? Integer.MAX_VALUE : post.getInt("count", 10);
+ final String counts = post.get("count", "");
+ int maxcount = (counts == null || counts.length() == 0 || counts.equals("all")) ? Integer.MAX_VALUE : post.getInt("count", 10);
final String pattern = post.get("search", "");
final Pattern matcher = (pattern.isEmpty() || pattern.equals(".*")) ? null : Pattern.compile(".*" + pattern + ".*");
@@ -102,7 +102,7 @@ public class table_p { if (post.containsKey("commitrow")) {
final String pk = post.get("pk");
- final Map<String, byte[]> map = new HashMap<String, byte[]>();
+ final Map<String, byte[]> map = new HashMap<>();
for (final Map.Entry<String, String> entry: post.entrySet()) {
if (entry.getKey().startsWith("col_")) {
map.put(entry.getKey().substring(4), entry.getValue().getBytes());
@@ -131,7 +131,7 @@ public class table_p { columns = sb.tables.columns(table);
} catch (final IOException e) {
ConcurrentLog.logException(e);
- columns = new ArrayList<String>();
+ columns = new ArrayList<>();
}
// if a row attribute is given
diff --git a/source/net/yacy/htroot/api/webstructure.java b/source/net/yacy/htroot/api/webstructure.java index a437277a5..c668d234e 100644 --- a/source/net/yacy/htroot/api/webstructure.java +++ b/source/net/yacy/htroot/api/webstructure.java @@ -114,13 +114,13 @@ public class webstructure { public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) { final serverObjects prop = new serverObjects(); final Switchboard sb = (Switchboard) env; - final String about = post == null ? null : post.get("about", null); // may be a URL, a URL hash or a domain hash + final String about = post == null ? null : post.get("about", ""); // may be a URL, a URL hash or a domain hash prop.put("out", 0); prop.put("in", 0); prop.put("references", 0); prop.put("citations", 0); final boolean authenticated = sb.adminAuthenticated(header) >= 2; - if (about != null) { + if (about != null && about.length() > 0) { DigestURL url = null; byte[] urlhash = null; final Set<String> hostHashes = new HashSet<>(); diff --git a/source/net/yacy/htroot/goto_p.java b/source/net/yacy/htroot/goto_p.java index 865f04d90..ec7d60548 100644 --- a/source/net/yacy/htroot/goto_p.java +++ b/source/net/yacy/htroot/goto_p.java @@ -53,10 +53,10 @@ public class goto_p { String hash = null;
if (post != null) {
- hash = post.get("hash", null); // get peers hash
+ hash = post.get("hash", ""); // get peers hash
}
- if (hash != null) {
+ if (hash != null && hash.length() > 0) {
final Seed seed = sb.peers.getConnected(hash);
if (seed != null) {
diff --git a/source/net/yacy/htroot/mediawiki_p.java b/source/net/yacy/htroot/mediawiki_p.java index 69b9fb0db..5c819b029 100644 --- a/source/net/yacy/htroot/mediawiki_p.java +++ b/source/net/yacy/htroot/mediawiki_p.java @@ -49,9 +49,9 @@ public class mediawiki_p { return post; } - final String dump = post.get("dump", null); - final String title = post.get("title", null); - if (dump == null || title == null) return post; + final String dump = post.get("dump", ""); + final String title = post.get("title", ""); + if (dump.length() == 0 || title.length() == 0) return post; final File dumpFile = new File(sb.getDataPath(), "DATA/HTCACHE/mediawiki/" + dump); diff --git a/source/net/yacy/htroot/rct_p.java b/source/net/yacy/htroot/rct_p.java index 7c7bf5384..511460678 100644 --- a/source/net/yacy/htroot/rct_p.java +++ b/source/net/yacy/htroot/rct_p.java @@ -53,8 +53,8 @@ public class rct_p { if (post != null) { if (post.containsKey("retrieve")) { - final String peerhash = post.get("peer", null); - final Seed seed = (peerhash == null) ? null : sb.peers.getConnected(peerhash); + final String peerhash = post.get("peer", ""); + final Seed seed = (peerhash.length() == 0) ? null : sb.peers.getConnected(peerhash); final boolean preferHttps = sb.getConfigBool(SwitchboardConstants.NETWORK_PROTOCOL_HTTPS_PREFERRED, SwitchboardConstants.NETWORK_PROTOCOL_HTTPS_PREFERRED_DEFAULT); final RSSFeed feed = (seed == null) ? null : Protocol.queryRemoteCrawlURLs(sb.peers, seed, 20, 60000, preferHttps); diff --git a/source/net/yacy/http/YaCyHttpServer.java b/source/net/yacy/http/YaCyHttpServer.java index 4d77254f3..55ac5476e 100644 --- a/source/net/yacy/http/YaCyHttpServer.java +++ b/source/net/yacy/http/YaCyHttpServer.java @@ -208,7 +208,7 @@ public class YaCyHttpServer { context.setServer(this.server); context.setContextPath("/"); context.setHandler(handlers); - context.setMaxFormContentSize(1024 * 1024 * 10); // allow 10MB, large forms may be required during crawl starts with long lists + context.setMaxFormContentSize(-1); final org.eclipse.jetty.util.log.Logger log = Log.getRootLogger(); context.setLogger(log); // make YaCy handlers (in context) and servlet context handlers available (both contain root context "/") diff --git a/source/net/yacy/http/servlets/YaCyDefaultServlet.java b/source/net/yacy/http/servlets/YaCyDefaultServlet.java index c9650f466..ef877f4b9 100644 --- a/source/net/yacy/http/servlets/YaCyDefaultServlet.java +++ b/source/net/yacy/http/servlets/YaCyDefaultServlet.java @@ -34,15 +34,11 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
-import java.util.AbstractMap;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.LinkedBlockingQueue;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
@@ -70,12 +66,12 @@ import com.google.common.net.HttpHeaders; import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.analysis.Classification;
-import net.yacy.cora.order.Base64Order;
import net.yacy.cora.protocol.Domains;
import net.yacy.cora.protocol.HeaderFramework;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.protocol.ResponseHeader;
import net.yacy.cora.util.ByteBuffer;
+import net.yacy.cora.util.ChunkedBytes;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.data.BadTransactionException;
import net.yacy.data.InvalidURLLicenceException;
@@ -1227,20 +1223,19 @@ public class YaCyDefaultServlet extends HttpServlet { protected void parseMultipart(final HttpServletRequest request, final serverObjects args) throws IOException {
// reject too large uploads
- if (request.getContentLength() > SIZE_FILE_THRESHOLD) throw new IOException("FileUploadException: uploaded file too large = " + request.getContentLength());
+ //if (request.getContentLength() > SIZE_FILE_THRESHOLD) throw new IOException("FileUploadException: uploaded file too large = " + request.getContentLength());
// check if we have enough memory
if (!MemoryControl.request(request.getContentLength() * 3, false)) {
throw new IOException("not enough memory available for request. request.getContentLength() = " + request.getContentLength() + ", MemoryControl.available() = " + MemoryControl.available());
}
final ServletFileUpload upload = new ServletFileUpload(DISK_FILE_ITEM_FACTORY);
- upload.setFileSizeMax(SIZE_FILE_THRESHOLD);
+ upload.setFileSizeMax(-1);
try {
// Parse the request to get form field items
final List<FileItem> fileItems = upload.parseRequest(request);
// Process the uploaded file items
final Iterator<FileItem> i = fileItems.iterator();
- final BlockingQueue<Map.Entry<String, byte[]>> files = new LinkedBlockingQueue<>();
while (i.hasNext()) {
final FileItem item = i.next();
if (item.isFormField()) {
@@ -1258,7 +1253,8 @@ public class YaCyDefaultServlet extends HttpServlet { InputStream filecontent = null;
try {
filecontent = item.getInputStream();
- files.put(new AbstractMap.SimpleEntry<>(item.getFieldName(), FileUtils.read(filecontent)));
+ String filename = item.getFieldName() + "$file";
+ args.put(filename, new ChunkedBytes(filecontent));
} catch (final IOException e) {
ConcurrentLog.info("FILEHANDLER", e.getMessage());
} finally {
@@ -1266,49 +1262,7 @@ public class YaCyDefaultServlet extends HttpServlet { }
}
}
- if (files.size() <= 1) { // TODO: should include additonal checks to limit parameter.size below rel. large SIZE_FILE_THRESHOLD
- for (final Map.Entry<String, byte[]> job: files) { // add the file content to parameter fieldname$file
- final String n = job.getKey();
- final byte[] v = job.getValue();
- final String filename = args.get(n);
- if (filename != null && (filename.endsWith(".gz") || filename.endsWith(".zip") || filename.endsWith(".warc") || filename.endsWith(".zim"))) {
- // transform this value into base64
- final String b64 = Base64Order.standardCoder.encode(v);
- args.put(n + "$file", b64);
- args.remove(n);
- args.put(n, filename + ".base64");
- } else {
- args.put(n + "$file", v); // the byte[] is transformed into UTF8. You cannot push binaries here
- }
- }
- } else {
- // do this concurrently (this would all be superfluous if serverObjects could store byte[] instead only String)
- final int t = Math.min(files.size(), Runtime.getRuntime().availableProcessors());
- final Map.Entry<String, byte[]> POISON = new AbstractMap.SimpleEntry<>(null, null);
- final Thread[] p = new Thread[t];
- for (int j = 0; j < t; j++) {
- files.put(POISON);
- p[j] = new Thread("YaCyDefaultServlet.parseMultipart-" + j) {
- @Override
- public void run() {
- Map.Entry<String, byte[]> job;
- try {while ((job = files.take()) != POISON) {
- final String n = job.getKey();
- final byte[] v = job.getValue();
- final String filename = args.get(n);
- final String b64 = Base64Order.standardCoder.encode(v);
- synchronized (args) {
- args.put(n + "$file", b64);
- args.remove(n);
- args.put(n, filename + ".base64");
- }
- }} catch (final InterruptedException e) {}
- }
- };
- p[j].start();
- }
- for (int j = 0; j < t; j++) p[j].join();
- }
+
} catch (final Exception ex) {
ConcurrentLog.info("FILEHANDLER", ex.getMessage());
}
diff --git a/source/net/yacy/server/serverObjects.java b/source/net/yacy/server/serverObjects.java index d28daad00..c71430528 100644 --- a/source/net/yacy/server/serverObjects.java +++ b/source/net/yacy/server/serverObjects.java @@ -47,6 +47,7 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.InetAddress; @@ -61,30 +62,31 @@ import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.FacetParams; +import org.apache.solr.common.params.MultiMapSolrParams; +import org.json.JSONObject; + import net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.document.id.MultiProtocolURL; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.protocol.RequestHeader.FileType; +import net.yacy.cora.util.ChunkedBytes; import net.yacy.document.parser.html.CharacterCoding; import net.yacy.kelondro.util.Formatter; import net.yacy.search.Switchboard; import net.yacy.search.schema.CollectionSchema; -import org.apache.solr.common.params.CommonParams; -import org.apache.solr.common.params.FacetParams; -import org.apache.solr.common.params.MultiMapSolrParams; -import org.json.JSONObject; - public class serverObjects implements Serializable, Cloneable { - + private static final long serialVersionUID = 3999165204849858546L; public static final String ACTION_AUTHENTICATE = "AUTHENTICATE"; - - /** Key for an URL redirection : should be associated with the redirected location. + + /** Key for an URL redirection : should be associated with the redirected location. * The main servlet handles this to produce an HTTP 302 status. */ public static final String ACTION_LOCATION = "LOCATION"; - + public final static String ADMIN_AUTHENTICATE_MSG = "admin log-in. If you don't know the password, set it with {yacyhome}/bin/passwd.sh {newpassword}"; private final static Pattern patternNewline = Pattern.compile("\n"); @@ -92,21 +94,21 @@ public class serverObjects implements Serializable, Cloneable { private boolean localized = true; private final static char BOM = '\uFEFF'; // ByteOrderMark character that may appear at beginnings of Strings (Browser may append that) - private final MultiMapSolrParams map; - + private final Map<String, ChunkedBytes[]> map; + public serverObjects() { super(); - this.map = new MultiMapSolrParams(new HashMap<String, String[]>()); + this.map = new HashMap<>(); } protected serverObjects(serverObjects o) { super(); this.map = o.map; } - - protected serverObjects(final Map<String, String[]> input) { + + protected serverObjects(final Map<String, ChunkedBytes[]> input) { super(); - this.map = new MultiMapSolrParams(input); + this.map = new HashMap<>(input); } public void authenticationRequired() { @@ -114,17 +116,17 @@ public class serverObjects implements Serializable, Cloneable { } public int size() { - return this.map.toNamedList().size() / 2; + return this.map.size(); } - + public void clear() { - this.map.getMap().clear(); + this.map.clear(); } public boolean isEmpty() { - return this.map.getMap().isEmpty(); + return this.map.isEmpty(); } - + private static final String removeByteOrderMark(final String s) { if (s == null || s.isEmpty()) return s; if (s.charAt(0) == BOM) return s.substring(1); @@ -132,43 +134,55 @@ public class serverObjects implements Serializable, Cloneable { } public boolean containsKey(String key) { - String[] arr = this.map.getParams(key); - return arr != null && arr.length > 0; + return this.map.containsKey(key); } - + public MultiMapSolrParams getSolrParams() { - return this.map; + MultiMapSolrParams emap = new MultiMapSolrParams(new HashMap<>()); + for (String key: this.keySet()) { + ChunkedBytes[] cbs = this.map.get(key); + String[] values = new String[cbs.length]; + for (int i = 0; i < cbs.length; i++) { + values[i] = UTF8.String(cbs[i].toByteArray()); + } + emap.getMap().put(key, values); + } + return emap; } public List<Map.Entry<String, String>> entrySet() { - List<Map.Entry<String, String>> set = new ArrayList<Map.Entry<String, String>>(this.map.getMap().size() * 2); - Set<Map.Entry<String, String[]>> mset = this.map.getMap().entrySet(); - for (Map.Entry<String, String[]> entry: mset) { - String[] vlist = entry.getValue(); - for (String v: vlist) set.add(new AbstractMap.SimpleEntry<String, String>(entry.getKey(), v)); + List<Map.Entry<String, String>> set = new ArrayList<>(this.map.size() * 2); + for (Map.Entry<String, ChunkedBytes[]> entry: this.map.entrySet()) { + ChunkedBytes[] vlist = entry.getValue(); + for (ChunkedBytes v: vlist) set.add(new AbstractMap.SimpleEntry<>(entry.getKey(), v.toString())); } return set; } - + public Set<String> values() { - Set<String> set = new HashSet<String>(this.map.getMap().size() * 2); - for (Map.Entry<String, String[]> entry: this.map.getMap().entrySet()) { - for (String v: entry.getValue()) set.add(v); + Set<String> set = new HashSet<>(this.map.size() * 2); + for (Map.Entry<String, ChunkedBytes[]> entry: this.map.entrySet()) { + for (ChunkedBytes v: entry.getValue()) set.add(v.toString()); } return set; } - + public Set<String> keySet() { - return this.map.getMap().keySet(); + return this.map.keySet(); } public String[] remove(String key) { - return this.map.getMap().remove(key); + ChunkedBytes[] cbs = this.map.remove(key); + String[] arr = new String[cbs == null ? 0 : cbs.length]; + if (cbs != null) { + for (int i = 0; i < arr.length; i++) arr[i] = cbs[i].toString(); + } + return arr; } - + public int remove(String key, int dflt) { - final String result = removeByteOrderMark(get(key)); - this.map.getMap().remove(key); + final String result = removeByteOrderMark(this.get(key)); + this.map.remove(key); if (result == null) return dflt; try { return Integer.parseInt(result); @@ -176,79 +190,63 @@ public class serverObjects implements Serializable, Cloneable { return dflt; } } - + public void putAll(Map<String, String> m) { for (Map.Entry<String, String> e: m.entrySet()) { - put(e.getKey(), e.getValue()); + this.put(e.getKey(), e.getValue()); } } public void add(final String key, final String value) { - if (key == null) { - // this does nothing - return; - } - if (value == null) { - return; - } - String[] a = map.getMap().get(key); + if (key == null) { return; } // this does nothing + if (value == null) { return; } + ChunkedBytes[] a = this.map.get(key); if (a == null) { - map.getMap().put(key, new String[]{value}); + this.map.put(key, new ChunkedBytes[]{new ChunkedBytes(value)}); return; } for (int i = 0; i < a.length; i++) { if (a[i].equals(value)) return; // double-check } - String[] aa = new String[a.length + 1]; + ChunkedBytes[] aa = new ChunkedBytes[a.length + 1]; System.arraycopy(a, 0, aa, 0, a.length); - aa[a.length] = value; - map.getMap().put(key, aa); + aa[a.length] = new ChunkedBytes(value); + this.map.put(key, aa); return; } public void put(final String key, final boolean value) { - put(key, value ? "1" : "0"); + this.put(key, value ? "1" : "0"); } - + + public void put(final String key, final ChunkedBytes value) { + if (key == null) { return; } + if (value == null) { this.map.remove(key); return; } // assigning the null value creates the same effect like removing the element + this.map.put(key, new ChunkedBytes[]{value}); + } + public void put(final String key, final String value) { - if (key == null) { - // this does nothing - return; - } - if (value == null) { - // assigning the null value creates the same effect like removing the element - map.getMap().remove(key); - return; - } - String[] a = map.getMap().get(key); - if (a == null) { - map.getMap().put(key, new String[]{value}); - return; - } - map.getMap().put(key, new String[]{value}); + if (key == null) { return; } + if (value == null) { this.map.remove(key); return; } // assigning the null value creates the same effect like removing the element + this.map.put(key, new ChunkedBytes[]{new ChunkedBytes(value)}); } public void add(final String key, final byte[] value) { if (value == null) return; - add(key, UTF8.String(value)); + this.add(key, UTF8.String(value)); } public void put(final String key, final byte[] value) { if (value == null) return; - put(key, UTF8.String(value)); + this.put(key, UTF8.String(value)); } public void put(final String key, final String[] values) { - if (key == null) { - // this does nothing - return; - } else if (values == null) { - // assigning the null value creates the same effect like removing the element - map.getMap().remove(key); - return; - } else { - map.getMap().put(key, values); - } + if (key == null) { return; } // this does nothing + if (values == null) { this.map.remove(key); return; } // assigning the null value creates the same effect like removing the element + ChunkedBytes[] cbs = new ChunkedBytes[values.length]; + for (int i = 0; i < values.length; i++) cbs[i] = new ChunkedBytes(values[i]); + this.map.put(key, cbs); } /** @@ -258,26 +256,26 @@ public class serverObjects implements Serializable, Cloneable { * @param value value as double/float. */ public void put(final String key, final float value) { - put(key, Float.toString(value)); + this.put(key, Float.toString(value)); } public void put(final String key, final double value) { - put(key, Double.toString(value)); + this.put(key, Double.toString(value)); } /** * same as {@link #put(String, double)} but for integer types */ public void put(final String key, final long value) { - put(key, Long.toString(value)); + this.put(key, Long.toString(value)); } public void put(final String key, final java.util.Date value) { - put(key, value.toString()); + this.put(key, value.toString()); } public void put(final String key, final InetAddress value) { - put(key, value.toString()); + this.put(key, value.toString()); } /** @@ -288,42 +286,42 @@ public class serverObjects implements Serializable, Cloneable { public void putJSON(final String key, String value) { value = JSONObject.quote(value); value = value.substring(1, value.length() - 1); - put(key, value); + this.put(key, value); } /** * Add a String to the map. The content of the string is first decoded to removed any URL encoding (application/x-www-form-urlencoded). - * Then the content of the String is escaped to be usable in HTML output. + * Then the content of the String is escaped to be usable in HTML output. * @param key key name as String. * @param value a String that will be reencoded for HTML output. * @see CharacterCoding#encodeUnicode2html(String, boolean) */ public void putHTML(final String key, final String value) { - put(key, value == null ? "" : CharacterCoding.unicode2html(UTF8.decodeURL(value), true)); + this.put(key, value == null ? "" : CharacterCoding.unicode2html(UTF8.decodeURL(value), true)); } /** * Add a String UTF-8 encoded bytes to the map. The content of the string is first decoded to removed any URL encoding (application/x-www-form-urlencoded). - * Then the content of the String is escaped to be usable in HTML output. + * Then the content of the String is escaped to be usable in HTML output. * @param key key name as String. * @param value the UTF-8 encoded byte array of a String that will be reencoded for HTML output. * @see CharacterCoding#encodeUnicode2html(String, boolean) */ public void putHTML(final String key, final byte[] value) { - putHTML(key, value == null ? "" : UTF8.String(value)); + this.putHTML(key, value == null ? "" : UTF8.String(value)); } - + /** * Add a String to the map. The eventual URL encoding * (application/x-www-form-urlencoded) is retained, but the String is still * escaped to be usable in HTML output. - * + * * @param key key name as String. * @param value a String that will be reencoded for HTML output. * @see CharacterCoding#encodeUnicode2html(String, boolean) */ public void putUrlEncodedHTML(final String key, final String value) { - put(key, value == null ? "" : CharacterCoding.unicode2html(value, true)); + this.put(key, value == null ? "" : CharacterCoding.unicode2html(value, true)); } /** @@ -333,7 +331,7 @@ public class serverObjects implements Serializable, Cloneable { * replaced in the returned String. */ public void putXML(final String key, final String value) { - put(key, value == null ? "" : CharacterCoding.unicode2xml(value, true)); + this.put(key, value == null ? "" : CharacterCoding.unicode2xml(value, true)); } /** @@ -343,26 +341,26 @@ public class serverObjects implements Serializable, Cloneable { * @param value */ public void put(final RequestHeader.FileType fileType, final String key, final String value) { - if (fileType == FileType.JSON) putJSON(key, value == null ? "" : value); - else if (fileType == FileType.XML) putXML(key, value == null ? "" : value); - else putHTML(key, value == null ? "" : value); + if (fileType == FileType.JSON) this.putJSON(key, value == null ? "" : value); + else if (fileType == FileType.XML) this.putXML(key, value == null ? "" : value); + else this.putHTML(key, value == null ? "" : value); } - + /** * Put the key/value pair, escaping characters depending on the target fileType. * The eventual URL encoding (application/x-www-form-urlencoded) is retained. - * + * * @param fileType the response target file type * @param key * @param value */ public void putUrlEncoded(final RequestHeader.FileType fileType, final String key, final String value) { if (fileType == FileType.JSON) { - putJSON(key, value == null ? "" : value); + this.putJSON(key, value == null ? "" : value); } else if (fileType == FileType.XML) { - putXML(key, value == null ? "" : value); + this.putXML(key, value == null ? "" : value); } else { - putUrlEncodedHTML(key, value == null ? "" : value); + this.putUrlEncodedHTML(key, value == null ? "" : value); } } @@ -404,7 +402,7 @@ public class serverObjects implements Serializable, Cloneable { public void putWiki(final String hostport, final String key, final String wikiCode){ this.put(key, Switchboard.wikiParser.transform(hostport, wikiCode)); } - + /** * Add a String to the map. The content of the String is first parsed and interpreted as Wiki code. * @param key key name as String. @@ -427,7 +425,7 @@ public class serverObjects implements Serializable, Cloneable { this.put(key, "Internal error pasting wiki-code: " + e.getMessage()); } } - + /** * Add a byte array to the map. The content of the array is first parsed and interpreted as Wiki code. * @param key key name as String. @@ -439,36 +437,70 @@ public class serverObjects implements Serializable, Cloneable { // inc variant: for counters public long inc(final String key) { - String c = get(key); + String c = this.get(key); if (c == null) c = "0"; final long l = Long.parseLong(c) + 1; - put(key, Long.toString(l)); + this.put(key, Long.toString(l)); return l; } public String[] getParams(String name) { - return map.getMap().get(name); + ChunkedBytes[] cbs = this.map.get(name); + String[] s = new String[cbs == null ? 0 : cbs.length]; + if (cbs != null) { + for (int i = 0; i < s.length; i++) s[i] = cbs[i].toString(); + } + return s; + } + + /** + * Get the content of a post field as a String + * This is a convenience method for the most common case. + * It returns an UTF-8 decoded String of maximum length 2GB. + * @param key + * @return + */ + public String get(String key) { + ChunkedBytes[] cbs = this.map.get(key); + return cbs == null || cbs.length == 0 ? null : cbs[0].toString(); } - public String get(String name) { - String[] arr = map.getMap().get(name); - return arr == null || arr.length == 0 ? null : arr[0]; + /** + * Get the content of a post field as a byte array + * This supports content a maximum length of 2GB. + * You should probably use getInputStream() instead. + * @param key + * @return + */ + public byte[] getBytes(final String key) { + ChunkedBytes[] cbs = this.map.get(key); + return cbs == null || cbs.length == 0 ? null : cbs[0].toByteArray(); } - - // new get with default objects - public Object get(final String key, final Object dflt) { - final Object result = get(key); - return (result == null) ? dflt : result; + + /** + * Get the content of a post field as an InputStream + * This supports content of arbitrary length (> 2GB) + * @param key + * @return + */ + public InputStream getInputStream(final String key) { + ChunkedBytes[] cbs = this.map.get(key); + return cbs == null || cbs.length == 0 ? null : cbs[0].openStream(); } // string variant public String get(final String key, final String dflt) { - final String result = removeByteOrderMark(get(key)); + final String result = removeByteOrderMark(this.get(key)); return (result == null) ? dflt : result; } + public ChunkedBytes get(final String key, final ChunkedBytes dflt) { + ChunkedBytes[] cbs = this.map.get(key); + return cbs == null || cbs.length == 0 ? null : cbs[0]; + } + public int getInt(final String key, final int dflt) { - final String s = removeByteOrderMark(get(key)); + final String s = removeByteOrderMark(this.get(key)); if (s == null) return dflt; try { return Integer.parseInt(s); @@ -478,7 +510,7 @@ public class serverObjects implements Serializable, Cloneable { } public long getLong(final String key, final long dflt) { - final String s = removeByteOrderMark(get(key)); + final String s = removeByteOrderMark(this.get(key)); if (s == null) return dflt; try { return Long.parseLong(s); @@ -488,7 +520,7 @@ public class serverObjects implements Serializable, Cloneable { } public float getFloat(final String key, final float dflt) { - final String s = removeByteOrderMark(get(key)); + final String s = removeByteOrderMark(this.get(key)); if (s == null) return dflt; try { return Float.parseFloat(s); @@ -498,7 +530,7 @@ public class serverObjects implements Serializable, Cloneable { } public double getDouble(final String key, final double dflt) { - final String s = removeByteOrderMark(get(key)); + final String s = removeByteOrderMark(this.get(key)); if (s == null) return dflt; try { return Double.parseDouble(s); @@ -517,23 +549,23 @@ public class serverObjects implements Serializable, Cloneable { * @return the boolean value of a field or false, if the field does not appear. */ public boolean getBoolean(final String key) { - String s = removeByteOrderMark(get(key)); + String s = removeByteOrderMark(this.get(key)); if (s == null) return false; s = s.toLowerCase(Locale.ROOT); return s.equals("true") || s.equals("on") || s.equals("1"); } /** - * @param keyMapper a regular expression for keys matching + * @param keyMapper a regular expression for keys matching * @return a set of all values where their key mappes the keyMapper * @throws PatternSyntaxException when the keyMapper syntax is not valid */ public String[] getAll(final String keyMapper) throws PatternSyntaxException { // the keyMapper may contain regular expressions as defined in String.matches // this method is particulary useful when parsing the result of checkbox forms - final List<String> v = new ArrayList<String>(); + final List<String> v = new ArrayList<>(); final Pattern keyPattern = Pattern.compile(keyMapper); - for (final Map.Entry<String, String> entry: entrySet()) { + for (final Map.Entry<String, String> entry: this.entrySet()) { if (keyPattern.matcher(entry.getKey()).matches()) { v.add(entry.getValue()); } @@ -541,7 +573,7 @@ public class serverObjects implements Serializable, Cloneable { return v.toArray(new String[0]); } - + /** * @param keyMapper a regular expression for keys matching * @return a map of keys/values where keys matches the keyMapper @@ -552,7 +584,7 @@ public class serverObjects implements Serializable, Cloneable { // this method is particulary useful when parsing the result of checkbox forms final Pattern keyPattern = Pattern.compile(keyMapper); final Map<String, String> map = new HashMap<>(); - for (final Map.Entry<String, String> entry: entrySet()) { + for (final Map.Entry<String, String> entry: this.entrySet()) { if (keyPattern.matcher(entry.getKey()).matches()) { map.put(entry.getKey(), entry.getValue()); } @@ -564,7 +596,7 @@ public class serverObjects implements Serializable, Cloneable { // put all elements of another hashtable into the own table public void putAll(final serverObjects add) { for (final Map.Entry<String, String> entry: add.entrySet()) { - put(entry.getKey(), entry.getValue()); + this.put(entry.getKey(), entry.getValue()); } } @@ -574,7 +606,7 @@ public class serverObjects implements Serializable, Cloneable { try { fos = new BufferedOutputStream(new FileOutputStream(f)); final StringBuilder line = new StringBuilder(64); - for (final Map.Entry<String, String> entry : entrySet()) { + for (final Map.Entry<String, String> entry : this.entrySet()) { line.delete(0, line.length()); line.append(entry.getKey()); line.append("="); @@ -606,7 +638,7 @@ public class serverObjects implements Serializable, Cloneable { @Override public Object clone() { - return new serverObjects(this.map.getMap()); + return new serverObjects(this.map); } /** @@ -614,9 +646,9 @@ public class serverObjects implements Serializable, Cloneable { */ @Override public String toString() { - if (this.map.getMap().isEmpty()) return ""; - final StringBuilder param = new StringBuilder(this.map.getMap().size() * 40); - for (final Map.Entry<String, String> entry: entrySet()) { + if (this.map.isEmpty()) return ""; + final StringBuilder param = new StringBuilder(this.map.size() * 40); + for (final Map.Entry<String, String> entry: this.entrySet()) { param.append(MultiProtocolURL.escape(entry.getKey())) .append('=') .append(MultiProtocolURL.escape(entry.getValue())) @@ -637,7 +669,7 @@ public class serverObjects implements Serializable, Cloneable { this.put("facet", "true"); for (int i = 0; i < facets.length; i++) this.add(FacetParams.FACET_FIELD, facets[i].getSolrFieldName()); } - return this.map; + return this.getSolrParams(); } } diff --git a/source/net/yacy/server/servletProperties.java b/source/net/yacy/server/servletProperties.java index 8879e92dd..c05c1821b 100644 --- a/source/net/yacy/server/servletProperties.java +++ b/source/net/yacy/server/servletProperties.java @@ -77,11 +77,6 @@ public class servletProperties extends serverObjects { } @Override - public Object get(final String key, final Object dflt) { - return super.get(this.prefix+key, dflt); - } - - @Override public String get(final String key, final String dflt) { return super.get(this.prefix+key, dflt); } |
