diff --git a/grails-encoder/src/main/groovy/org/grails/buffer/StreamByteBuffer.java b/grails-encoder/src/main/groovy/org/grails/buffer/StreamByteBuffer.java index 84ca9f565b3..31637353bd4 100644 --- a/grails-encoder/src/main/groovy/org/grails/buffer/StreamByteBuffer.java +++ b/grails-encoder/src/main/groovy/org/grails/buffer/StreamByteBuffer.java @@ -1,438 +1,438 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.buffer; - -import grails.util.GrailsArrayUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; -import java.nio.charset.CodingErrorAction; -import java.util.Iterator; -import java.util.LinkedList; - - -/** - * An in-memory buffer that provides OutputStream and InputStream interfaces. - * - * This is more efficient than using ByteArrayOutputStream/ByteArrayInputStream - * - * This is not thread-safe, it is intended to be used by a single Thread. - * - * @author Lari Hotari, Sagire Software Oy - */ -public class StreamByteBuffer { - - private static final int DEFAULT_CHUNK_SIZE = 8192; - - private LinkedList chunks = new LinkedList(); - private StreamByteBufferChunk currentWriteChunk; - private StreamByteBufferChunk currentReadChunk = null; - private int chunkSize; - private StreamByteBufferOutputStream output; - private StreamByteBufferInputStream input; - private int totalBytesUnreadInList = 0; - private int totalBytesUnreadInIterator = 0; - private ReadMode readMode; - private Iterator readIterator; - - public enum ReadMode { - REMOVE_AFTER_READING, - RETAIN_AFTER_READING - } - - public StreamByteBuffer() { - this(DEFAULT_CHUNK_SIZE); - } - - public StreamByteBuffer(int chunkSize) { - this(chunkSize, ReadMode.REMOVE_AFTER_READING); - } - - public StreamByteBuffer(int chunkSize, ReadMode readMode) { - this.chunkSize = chunkSize; - this.readMode = readMode; - currentWriteChunk = new StreamByteBufferChunk(chunkSize); - output = new StreamByteBufferOutputStream(); - input = new StreamByteBufferInputStream(); - } - - public OutputStream getOutputStream() { - return output; - } - - public InputStream getInputStream() { - return input; - } - - public void writeTo(OutputStream target) throws IOException { - while (prepareRead() != -1) { - currentReadChunk.writeTo(target); - } - } - - public byte[] readAsByteArray() { - byte[] buf = new byte[totalBytesUnread()]; - input.readImpl(buf, 0, buf.length); - return buf; - } - - public String readAsString(String encoding) throws CharacterCodingException { - Charset charset = Charset.forName(encoding); - return readAsString(charset); - } - - public String readAsString(Charset charset) throws CharacterCodingException { - int unreadSize = totalBytesUnread(); - if (unreadSize > 0) { - CharsetDecoder decoder = charset.newDecoder().onMalformedInput( - CodingErrorAction.REPLACE).onUnmappableCharacter( - CodingErrorAction.REPLACE); - CharBuffer charbuffer = CharBuffer.allocate(unreadSize); - ByteBuffer buf = null; - while (prepareRead() != -1) { - buf = currentReadChunk.readToNioBuffer(); - boolean endOfInput = (prepareRead() == -1); - CoderResult result = decoder.decode(buf, charbuffer, endOfInput); - if (endOfInput) { - if (!result.isUnderflow()) { - result.throwException(); - } - } - } - CoderResult result = decoder.flush(charbuffer); - if (buf.hasRemaining()) { - throw new IllegalStateException("There's a bug here, buffer wasn't read fully."); - } - if (!result.isUnderflow()) { - result.throwException(); - } - charbuffer.flip(); - String str; - if (charbuffer.hasArray()) { - int len = charbuffer.remaining(); - char[] ch = charbuffer.array(); - if (len != ch.length) { - ch = (char[])GrailsArrayUtils.subarray(ch, 0, len); - } - str = StringCharArrayAccessor.createString(ch); - } - else { - str = charbuffer.toString(); - } - return str; - } - return null; - } - - public int totalBytesUnread() { - int total = 0; - if (readMode == ReadMode.REMOVE_AFTER_READING) { - total = totalBytesUnreadInList; - } - else if (readMode == ReadMode.RETAIN_AFTER_READING) { - prepareRetainAfterReading(); - total = totalBytesUnreadInIterator; - } - if (currentReadChunk != null) { - total += currentReadChunk.bytesUnread(); - } - if (currentWriteChunk != currentReadChunk && currentWriteChunk != null) { - if (readMode == ReadMode.REMOVE_AFTER_READING) { - total += currentWriteChunk.bytesUnread(); - } - else if (readMode == ReadMode.RETAIN_AFTER_READING) { - total += currentWriteChunk.bytesUsed(); - } - } - return total; - } - - protected int allocateSpace() { - int spaceLeft = currentWriteChunk.spaceLeft(); - if (spaceLeft == 0) { - chunks.add(currentWriteChunk); - totalBytesUnreadInList += currentWriteChunk.bytesUnread(); - currentWriteChunk = new StreamByteBufferChunk(chunkSize); - spaceLeft = currentWriteChunk.spaceLeft(); - } - return spaceLeft; - } - - protected int prepareRead() { - prepareRetainAfterReading(); - int bytesUnread = (currentReadChunk != null) ? currentReadChunk.bytesUnread() : 0; - if (bytesUnread == 0) { - if (readMode == ReadMode.REMOVE_AFTER_READING && !chunks.isEmpty()) { - currentReadChunk = chunks.removeFirst(); - bytesUnread = currentReadChunk.bytesUnread(); - totalBytesUnreadInList -= bytesUnread; - } - else if (readMode == ReadMode.RETAIN_AFTER_READING && readIterator.hasNext()) { - currentReadChunk = readIterator.next(); - currentReadChunk.reset(); - bytesUnread = currentReadChunk.bytesUnread(); - totalBytesUnreadInIterator -= bytesUnread; - } - else if (currentReadChunk != currentWriteChunk) { - currentReadChunk = currentWriteChunk; - bytesUnread = currentReadChunk.bytesUnread(); - } - else { - bytesUnread = -1; - } - } - return bytesUnread; - } - - public void reset() { - if (readMode == ReadMode.RETAIN_AFTER_READING) { - readIterator = null; - prepareRetainAfterReading(); - if (currentWriteChunk != null) { - currentWriteChunk.reset(); - } - } - } - - private void prepareRetainAfterReading() { - if (readMode == ReadMode.RETAIN_AFTER_READING && readIterator == null) { - readIterator = chunks.iterator(); - totalBytesUnreadInIterator = totalBytesUnreadInList; - currentReadChunk = null; - } - } - - public ReadMode getReadMode() { - return readMode; - } - - public void setReadMode(ReadMode readMode) { - this.readMode = readMode; - } - - public void retainAfterReadingMode() { - setReadMode(ReadMode.RETAIN_AFTER_READING); - } - - class StreamByteBufferChunk { - private int pointer = 0; - private byte[] buffer; - private int size; - private int used = 0; - - public StreamByteBufferChunk(int size) { - this.size = size; - buffer = new byte[size]; - } - - public ByteBuffer readToNioBuffer() { - if (pointer < used) { - ByteBuffer result; - if (pointer > 0 || used < size) { - result = ByteBuffer.wrap(buffer, pointer, used - pointer); - } - else { - result = ByteBuffer.wrap(buffer); - } - pointer = used; - return result; - } - - return null; - } - - public boolean write(byte b) { - if (used < size) { - buffer[used++] = b; - return true; - } - - return false; - } - - public void write(byte[] b, int off, int len) { - System.arraycopy(b, off, buffer, used, len); - used = used + len; - } - - public void read(byte[] b, int off, int len) { - System.arraycopy(buffer, pointer, b, off, len); - pointer = pointer + len; - } - - public void writeTo(OutputStream target) throws IOException { - if (pointer < used) { - target.write(buffer, pointer, used - pointer); - pointer = used; - } - } - - public void reset() { - pointer = 0; - } - - public int bytesUsed() { - return used; - } - - public int bytesUnread() { - return used - pointer; - } - - public int read() { - if (pointer < used) { - return buffer[pointer++] & 0xff; - } - - return -1; - } - - public int spaceLeft() { - return size - used; - } - } - - class StreamByteBufferOutputStream extends OutputStream { - private boolean closed = false; - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (b == null) { - throw new NullPointerException(); - } - - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) > b.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } - - if (len == 0) { - return; - } - - int bytesLeft = len; - int currentOffset = off; - while (bytesLeft > 0) { - int spaceLeft = allocateSpace(); - int writeBytes = Math.min(spaceLeft, bytesLeft); - currentWriteChunk.write(b, currentOffset, writeBytes); - bytesLeft -= writeBytes; - currentOffset += writeBytes; - } - } - - @Override - public void close() throws IOException { - closed = true; - } - - public boolean isClosed() { - return closed; - } - - @Override - public void write(int b) throws IOException { - allocateSpace(); - currentWriteChunk.write((byte) b); - } - - public StreamByteBuffer getBuffer() { - return StreamByteBuffer.this; - } - } - - class StreamByteBufferInputStream extends InputStream { - @Override - public int read() throws IOException { - prepareRead(); - return currentReadChunk.read(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - return readImpl(b, off, len); - } - - int readImpl(byte[] b, int off, int len) { - if (b == null) { - throw new NullPointerException(); - } - - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) > b.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } - - if (len == 0) { - return 0; - } - - int bytesLeft = len; - int currentOffset = off; - int bytesUnread = prepareRead(); - int totalBytesRead = 0; - while (bytesLeft > 0 && bytesUnread != -1) { - int readBytes = Math.min(bytesUnread, bytesLeft); - currentReadChunk.read(b, currentOffset, readBytes); - bytesLeft -= readBytes; - currentOffset += readBytes; - totalBytesRead += readBytes; - bytesUnread = prepareRead(); - } - if (totalBytesRead > 0) { - return totalBytesRead; - } - - return -1; - } - - @Override - public synchronized void reset() throws IOException { - if (readMode==ReadMode.RETAIN_AFTER_READING) { - StreamByteBuffer.this.reset(); - } - else { - // reset isn't supported in ReadMode.REMOVE_AFTER_READING - super.reset(); - } - } - - @Override - public int available() throws IOException { - return totalBytesUnread(); - } - - public StreamByteBuffer getBuffer() { - return StreamByteBuffer.this; - } - } - - public void clear() { - chunks.clear(); - currentReadChunk = null; - totalBytesUnreadInList = 0; - totalBytesUnreadInIterator = 0; - currentWriteChunk = new StreamByteBufferChunk(chunkSize); - readIterator = null; - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.buffer; + +import grails.util.GrailsArrayUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Iterator; +import java.util.LinkedList; + + +/** + * An in-memory buffer that provides OutputStream and InputStream interfaces. + * + * This is more efficient than using ByteArrayOutputStream/ByteArrayInputStream + * + * This is not thread-safe, it is intended to be used by a single Thread. + * + * @author Lari Hotari, Sagire Software Oy + */ +public class StreamByteBuffer { + + private static final int DEFAULT_CHUNK_SIZE = 8192; + + private LinkedList chunks = new LinkedList(); + private StreamByteBufferChunk currentWriteChunk; + private StreamByteBufferChunk currentReadChunk = null; + private int chunkSize; + private StreamByteBufferOutputStream output; + private StreamByteBufferInputStream input; + private int totalBytesUnreadInList = 0; + private int totalBytesUnreadInIterator = 0; + private ReadMode readMode; + private Iterator readIterator; + + public enum ReadMode { + REMOVE_AFTER_READING, + RETAIN_AFTER_READING + } + + public StreamByteBuffer() { + this(DEFAULT_CHUNK_SIZE); + } + + public StreamByteBuffer(int chunkSize) { + this(chunkSize, ReadMode.REMOVE_AFTER_READING); + } + + public StreamByteBuffer(int chunkSize, ReadMode readMode) { + this.chunkSize = chunkSize; + this.readMode = readMode; + currentWriteChunk = new StreamByteBufferChunk(chunkSize); + output = new StreamByteBufferOutputStream(); + input = new StreamByteBufferInputStream(); + } + + public OutputStream getOutputStream() { + return output; + } + + public InputStream getInputStream() { + return input; + } + + public void writeTo(OutputStream target) throws IOException { + while (prepareRead() != -1) { + currentReadChunk.writeTo(target); + } + } + + public byte[] readAsByteArray() { + byte[] buf = new byte[totalBytesUnread()]; + input.readImpl(buf, 0, buf.length); + return buf; + } + + public String readAsString(String encoding) throws CharacterCodingException { + Charset charset = Charset.forName(encoding); + return readAsString(charset); + } + + public String readAsString(Charset charset) throws CharacterCodingException { + int unreadSize = totalBytesUnread(); + if (unreadSize > 0) { + CharsetDecoder decoder = charset.newDecoder().onMalformedInput( + CodingErrorAction.REPLACE).onUnmappableCharacter( + CodingErrorAction.REPLACE); + CharBuffer charbuffer = CharBuffer.allocate(unreadSize); + ByteBuffer buf = null; + while (prepareRead() != -1) { + buf = currentReadChunk.readToNioBuffer(); + boolean endOfInput = (prepareRead() == -1); + CoderResult result = decoder.decode(buf, charbuffer, endOfInput); + if (endOfInput) { + if (!result.isUnderflow()) { + result.throwException(); + } + } + } + CoderResult result = decoder.flush(charbuffer); + if (buf.hasRemaining()) { + throw new IllegalStateException("There's a bug here, buffer wasn't read fully."); + } + if (!result.isUnderflow()) { + result.throwException(); + } + charbuffer.flip(); + String str; + if (charbuffer.hasArray()) { + int len = charbuffer.remaining(); + char[] ch = charbuffer.array(); + if (len != ch.length) { + ch = (char[])GrailsArrayUtils.subarray(ch, 0, len); + } + str = StringCharArrayAccessor.createString(ch); + } + else { + str = charbuffer.toString(); + } + return str; + } + return null; + } + + public int totalBytesUnread() { + int total = 0; + if (readMode == ReadMode.REMOVE_AFTER_READING) { + total = totalBytesUnreadInList; + } + else if (readMode == ReadMode.RETAIN_AFTER_READING) { + prepareRetainAfterReading(); + total = totalBytesUnreadInIterator; + } + if (currentReadChunk != null) { + total += currentReadChunk.bytesUnread(); + } + if (currentWriteChunk != currentReadChunk && currentWriteChunk != null) { + if (readMode == ReadMode.REMOVE_AFTER_READING) { + total += currentWriteChunk.bytesUnread(); + } + else if (readMode == ReadMode.RETAIN_AFTER_READING) { + total += currentWriteChunk.bytesUsed(); + } + } + return total; + } + + protected int allocateSpace() { + int spaceLeft = currentWriteChunk.spaceLeft(); + if (spaceLeft == 0) { + chunks.add(currentWriteChunk); + totalBytesUnreadInList += currentWriteChunk.bytesUnread(); + currentWriteChunk = new StreamByteBufferChunk(chunkSize); + spaceLeft = currentWriteChunk.spaceLeft(); + } + return spaceLeft; + } + + protected int prepareRead() { + prepareRetainAfterReading(); + int bytesUnread = (currentReadChunk != null) ? currentReadChunk.bytesUnread() : 0; + if (bytesUnread == 0) { + if (readMode == ReadMode.REMOVE_AFTER_READING && !chunks.isEmpty()) { + currentReadChunk = chunks.removeFirst(); + bytesUnread = currentReadChunk.bytesUnread(); + totalBytesUnreadInList -= bytesUnread; + } + else if (readMode == ReadMode.RETAIN_AFTER_READING && readIterator.hasNext()) { + currentReadChunk = readIterator.next(); + currentReadChunk.reset(); + bytesUnread = currentReadChunk.bytesUnread(); + totalBytesUnreadInIterator -= bytesUnread; + } + else if (currentReadChunk != currentWriteChunk) { + currentReadChunk = currentWriteChunk; + bytesUnread = currentReadChunk.bytesUnread(); + } + else { + bytesUnread = -1; + } + } + return bytesUnread; + } + + public void reset() { + if (readMode == ReadMode.RETAIN_AFTER_READING) { + readIterator = null; + prepareRetainAfterReading(); + if (currentWriteChunk != null) { + currentWriteChunk.reset(); + } + } + } + + private void prepareRetainAfterReading() { + if (readMode == ReadMode.RETAIN_AFTER_READING && readIterator == null) { + readIterator = chunks.iterator(); + totalBytesUnreadInIterator = totalBytesUnreadInList; + currentReadChunk = null; + } + } + + public ReadMode getReadMode() { + return readMode; + } + + public void setReadMode(ReadMode readMode) { + this.readMode = readMode; + } + + public void retainAfterReadingMode() { + setReadMode(ReadMode.RETAIN_AFTER_READING); + } + + class StreamByteBufferChunk { + private int pointer = 0; + private byte[] buffer; + private int size; + private int used = 0; + + public StreamByteBufferChunk(int size) { + this.size = size; + buffer = new byte[size]; + } + + public ByteBuffer readToNioBuffer() { + if (pointer < used) { + ByteBuffer result; + if (pointer > 0 || used < size) { + result = ByteBuffer.wrap(buffer, pointer, used - pointer); + } + else { + result = ByteBuffer.wrap(buffer); + } + pointer = used; + return result; + } + + return null; + } + + public boolean write(byte b) { + if (used < size) { + buffer[used++] = b; + return true; + } + + return false; + } + + public void write(byte[] b, int off, int len) { + System.arraycopy(b, off, buffer, used, len); + used = used + len; + } + + public void read(byte[] b, int off, int len) { + System.arraycopy(buffer, pointer, b, off, len); + pointer = pointer + len; + } + + public void writeTo(OutputStream target) throws IOException { + if (pointer < used) { + target.write(buffer, pointer, used - pointer); + pointer = used; + } + } + + public void reset() { + pointer = 0; + } + + public int bytesUsed() { + return used; + } + + public int bytesUnread() { + return used - pointer; + } + + public int read() { + if (pointer < used) { + return buffer[pointer++] & 0xff; + } + + return -1; + } + + public int spaceLeft() { + return size - used; + } + } + + class StreamByteBufferOutputStream extends OutputStream { + private boolean closed = false; + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + + if (len == 0) { + return; + } + + int bytesLeft = len; + int currentOffset = off; + while (bytesLeft > 0) { + int spaceLeft = allocateSpace(); + int writeBytes = Math.min(spaceLeft, bytesLeft); + currentWriteChunk.write(b, currentOffset, writeBytes); + bytesLeft -= writeBytes; + currentOffset += writeBytes; + } + } + + @Override + public void close() throws IOException { + closed = true; + } + + public boolean isClosed() { + return closed; + } + + @Override + public void write(int b) throws IOException { + allocateSpace(); + currentWriteChunk.write((byte) b); + } + + public StreamByteBuffer getBuffer() { + return StreamByteBuffer.this; + } + } + + class StreamByteBufferInputStream extends InputStream { + @Override + public int read() throws IOException { + prepareRead(); + return currentReadChunk.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return readImpl(b, off, len); + } + + int readImpl(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } + + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + + if (len == 0) { + return 0; + } + + int bytesLeft = len; + int currentOffset = off; + int bytesUnread = prepareRead(); + int totalBytesRead = 0; + while (bytesLeft > 0 && bytesUnread != -1) { + int readBytes = Math.min(bytesUnread, bytesLeft); + currentReadChunk.read(b, currentOffset, readBytes); + bytesLeft -= readBytes; + currentOffset += readBytes; + totalBytesRead += readBytes; + bytesUnread = prepareRead(); + } + if (totalBytesRead > 0) { + return totalBytesRead; + } + + return -1; + } + + @Override + public synchronized void reset() throws IOException { + if (readMode==ReadMode.RETAIN_AFTER_READING) { + StreamByteBuffer.this.reset(); + } + else { + // reset isn't supported in ReadMode.REMOVE_AFTER_READING + super.reset(); + } + } + + @Override + public int available() throws IOException { + return totalBytesUnread(); + } + + public StreamByteBuffer getBuffer() { + return StreamByteBuffer.this; + } + } + + public void clear() { + chunks.clear(); + currentReadChunk = null; + totalBytesUnreadInList = 0; + totalBytesUnreadInIterator = 0; + currentWriteChunk = new StreamByteBufferChunk(chunkSize); + readIterator = null; + } +} diff --git a/grails-encoder/src/main/groovy/org/grails/encoder/impl/URLCodecFactory.groovy b/grails-encoder/src/main/groovy/org/grails/encoder/impl/URLCodecFactory.groovy index 26e388a2120..704312aa5fb 100644 --- a/grails-encoder/src/main/groovy/org/grails/encoder/impl/URLCodecFactory.groovy +++ b/grails-encoder/src/main/groovy/org/grails/encoder/impl/URLCodecFactory.groovy @@ -1,76 +1,76 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.encoder.impl; - -import groovy.transform.CompileStatic -import org.grails.encoder.CodecFactory -import org.grails.encoder.CodecIdentifier -import org.grails.encoder.Decoder -import org.grails.encoder.DefaultCodecIdentifier -import org.grails.encoder.Encoder - -/** - * Implements the 'www-form-urlencoded' encoding scheme, also misleadingly known as URL encoding. - * - * @see Chapter 17.13.4 Form content types - * of the HTML 4.01 Specification - * - * @since 2.4 - */ -@CompileStatic -public class URLCodecFactory implements CodecFactory { - static final CodecIdentifier URL_CODEC_IDENTIFIER = new DefaultCodecIdentifier("URL"); - - Encoder encoder = new Encoder() { - @Override - public CodecIdentifier getCodecIdentifier() { - URL_CODEC_IDENTIFIER; - } - - public Object encode(Object o) { - if(o==null) return o; - URLEncoder.encode(String.valueOf(o), resolveEncoding()); - } - - public boolean isApplyToSafelyEncoded() { - true; - } - - public boolean isSafe() { - true; - } - - public void markEncoded(CharSequence string) { - - } - }; - - Decoder decoder = new Decoder() { - public CodecIdentifier getCodecIdentifier() { - URL_CODEC_IDENTIFIER; - } - - @Override - public Object decode(Object o) { - if(o==null) return o; - URLDecoder.decode(String.valueOf(o), resolveEncoding()); - } - }; - - protected String resolveEncoding() { - 'UTF-8' - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.encoder.impl; + +import groovy.transform.CompileStatic +import org.grails.encoder.CodecFactory +import org.grails.encoder.CodecIdentifier +import org.grails.encoder.Decoder +import org.grails.encoder.DefaultCodecIdentifier +import org.grails.encoder.Encoder + +/** + * Implements the 'www-form-urlencoded' encoding scheme, also misleadingly known as URL encoding. + * + * @see Chapter 17.13.4 Form content types + * of the HTML 4.01 Specification + * + * @since 2.4 + */ +@CompileStatic +public class URLCodecFactory implements CodecFactory { + static final CodecIdentifier URL_CODEC_IDENTIFIER = new DefaultCodecIdentifier("URL"); + + Encoder encoder = new Encoder() { + @Override + public CodecIdentifier getCodecIdentifier() { + URL_CODEC_IDENTIFIER; + } + + public Object encode(Object o) { + if(o==null) return o; + URLEncoder.encode(String.valueOf(o), resolveEncoding()); + } + + public boolean isApplyToSafelyEncoded() { + true; + } + + public boolean isSafe() { + true; + } + + public void markEncoded(CharSequence string) { + + } + }; + + Decoder decoder = new Decoder() { + public CodecIdentifier getCodecIdentifier() { + URL_CODEC_IDENTIFIER; + } + + @Override + public Object decode(Object o) { + if(o==null) return o; + URLDecoder.decode(String.valueOf(o), resolveEncoding()); + } + }; + + protected String resolveEncoding() { + 'UTF-8' + } +} diff --git a/grails-plugin-codecs/src/main/groovy/org/grails/commons/CodecArtefactHandler.java b/grails-plugin-codecs/src/main/groovy/org/grails/commons/CodecArtefactHandler.java index e3301fc1f89..1f134ee6f3f 100644 --- a/grails-plugin-codecs/src/main/groovy/org/grails/commons/CodecArtefactHandler.java +++ b/grails-plugin-codecs/src/main/groovy/org/grails/commons/CodecArtefactHandler.java @@ -1,38 +1,38 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.commons; - -import grails.core.ArtefactHandlerAdapter; -import org.grails.core.artefact.DomainClassArtefactHandler; - -/** - * @author Marc Palmer (marc@anyware.co.uk) - */ -public class CodecArtefactHandler extends ArtefactHandlerAdapter { - - public static final String TYPE = "Codec"; - - public CodecArtefactHandler() { - super(TYPE, GrailsCodecClass.class, DefaultGrailsCodecClass.class, TYPE); - } - - @Override - public boolean isArtefactClass(@SuppressWarnings("rawtypes") Class clazz) { - if (clazz == null) return false; - - return clazz.getName().endsWith(DefaultGrailsCodecClass.CODEC) && !DomainClassArtefactHandler.isDomainClass(clazz); - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.commons; + +import grails.core.ArtefactHandlerAdapter; +import org.grails.core.artefact.DomainClassArtefactHandler; + +/** + * @author Marc Palmer (marc@anyware.co.uk) + */ +public class CodecArtefactHandler extends ArtefactHandlerAdapter { + + public static final String TYPE = "Codec"; + + public CodecArtefactHandler() { + super(TYPE, GrailsCodecClass.class, DefaultGrailsCodecClass.class, TYPE); + } + + @Override + public boolean isArtefactClass(@SuppressWarnings("rawtypes") Class clazz) { + if (clazz == null) return false; + + return clazz.getName().endsWith(DefaultGrailsCodecClass.CODEC) && !DomainClassArtefactHandler.isDomainClass(clazz); + } +} diff --git a/grails-plugin-codecs/src/main/groovy/org/grails/plugins/codecs/URLCodec.groovy b/grails-plugin-codecs/src/main/groovy/org/grails/plugins/codecs/URLCodec.groovy index 958c8d97b80..09b9e96aeff 100644 --- a/grails-plugin-codecs/src/main/groovy/org/grails/plugins/codecs/URLCodec.groovy +++ b/grails-plugin-codecs/src/main/groovy/org/grails/plugins/codecs/URLCodec.groovy @@ -1,31 +1,31 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.plugins.codecs - -import org.grails.encoder.impl.URLCodecFactory -import org.springframework.web.context.request.RequestContextHolder - -/** - * A codec that encodes and decodes Objects to and from URL encoded strings. - * - * @author Marc Palmer - * @since 0.5 - */ -class URLCodec extends URLCodecFactory { - protected String resolveEncoding() { - RequestContextHolder.getRequestAttributes()?.request?.characterEncoding ?: 'UTF-8' - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.plugins.codecs + +import org.grails.encoder.impl.URLCodecFactory +import org.springframework.web.context.request.RequestContextHolder + +/** + * A codec that encodes and decodes Objects to and from URL encoded strings. + * + * @author Marc Palmer + * @since 0.5 + */ +class URLCodec extends URLCodecFactory { + protected String resolveEncoding() { + RequestContextHolder.getRequestAttributes()?.request?.characterEncoding ?: 'UTF-8' + } +} diff --git a/grails-spring/src/main/groovy/org/grails/spring/BeanConfiguration.java b/grails-spring/src/main/groovy/org/grails/spring/BeanConfiguration.java index fc6dc318ab6..3bc887e3a20 100644 --- a/grails-spring/src/main/groovy/org/grails/spring/BeanConfiguration.java +++ b/grails-spring/src/main/groovy/org/grails/spring/BeanConfiguration.java @@ -1,141 +1,141 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.spring; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; - -/** - * Represents a runtime bean configuration. - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - */ -public interface BeanConfiguration { - - String AUTOWIRE_BY_TYPE = "byType"; - String AUTOWIRE_BY_NAME = "byName"; - - /** - * @return The name of the bean - */ - String getName(); - - /** - * @return true if the bean is singleton - */ - boolean isSingleton(); - - /** - * @return The Spring bean definition instance - */ - AbstractBeanDefinition getBeanDefinition(); - - /** - * Adds a property value to this bean. - * @param propertyName The name of the property - * @param propertyValue The value of the property - * - * @return Returns this bean configuration - */ - BeanConfiguration addProperty(String propertyName, Object propertyValue); - - /** - * Sets the name of the method to call when destroying the bean. - * - * @param methodName The method name - * @return This bean configuration - */ - BeanConfiguration setDestroyMethod(String methodName); - - /** - * Sets the names of the beans this bean configuration depends on - * - * @param dependsOn Bean names it depends on - * @return This bean configuration - */ - BeanConfiguration setDependsOn(String[] dependsOn); - - /** - * - * @param beanName - * @return This BeanConfiguration - */ - BeanConfiguration setFactoryBean(String beanName); - - /** - * - * @param methodName - * @return This BeanConfiguration - */ - BeanConfiguration setFactoryMethod(String methodName); - - /** - * Sets the autowire type, either "byType" or "byName" - * - * @param type The type - * @return This BeanConfiguration - */ - BeanConfiguration setAutowire(String type); - - /** - * Sets the name of the bean in the app ctx. - * @param beanName The bean name - */ - void setName(String beanName); - - /** - * Returns true if the bean config has the name property set. - * @param name The name of the property - * @return true if it does have a property with the given name - */ - boolean hasProperty(String name); - - /** - * Returns the value of the given property or throws a MissingPropertyException. - * - * @param name The name of the property - * @return The value of the property - */ - Object getPropertyValue(String name); - - /** - * Sets a property value on the bean configuration - * - * @param property The name of the property - * @param newValue The value - */ - void setPropertyValue(String property, Object newValue); - - /** - * Sets the BeanConfiguration as an Abstract bean definition - * @param isAbstract Whether its abstract or not - * @return This BeanConfiguration object - */ - BeanConfiguration setAbstract(boolean isAbstract); - - /** - * Sets the name of the parent bean. - * - * @param name Either a string which is the name of the bean, a RuntimeBeanReference or a BeanConfiguration - */ - void setParent(Object name); - - void setBeanDefinition(BeanDefinition definition); -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.spring; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; + +/** + * Represents a runtime bean configuration. + * + * Credit must go to Solomon Duskis and the + * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring + * + * @author Graeme + * @since 0.3 + */ +public interface BeanConfiguration { + + String AUTOWIRE_BY_TYPE = "byType"; + String AUTOWIRE_BY_NAME = "byName"; + + /** + * @return The name of the bean + */ + String getName(); + + /** + * @return true if the bean is singleton + */ + boolean isSingleton(); + + /** + * @return The Spring bean definition instance + */ + AbstractBeanDefinition getBeanDefinition(); + + /** + * Adds a property value to this bean. + * @param propertyName The name of the property + * @param propertyValue The value of the property + * + * @return Returns this bean configuration + */ + BeanConfiguration addProperty(String propertyName, Object propertyValue); + + /** + * Sets the name of the method to call when destroying the bean. + * + * @param methodName The method name + * @return This bean configuration + */ + BeanConfiguration setDestroyMethod(String methodName); + + /** + * Sets the names of the beans this bean configuration depends on + * + * @param dependsOn Bean names it depends on + * @return This bean configuration + */ + BeanConfiguration setDependsOn(String[] dependsOn); + + /** + * + * @param beanName + * @return This BeanConfiguration + */ + BeanConfiguration setFactoryBean(String beanName); + + /** + * + * @param methodName + * @return This BeanConfiguration + */ + BeanConfiguration setFactoryMethod(String methodName); + + /** + * Sets the autowire type, either "byType" or "byName" + * + * @param type The type + * @return This BeanConfiguration + */ + BeanConfiguration setAutowire(String type); + + /** + * Sets the name of the bean in the app ctx. + * @param beanName The bean name + */ + void setName(String beanName); + + /** + * Returns true if the bean config has the name property set. + * @param name The name of the property + * @return true if it does have a property with the given name + */ + boolean hasProperty(String name); + + /** + * Returns the value of the given property or throws a MissingPropertyException. + * + * @param name The name of the property + * @return The value of the property + */ + Object getPropertyValue(String name); + + /** + * Sets a property value on the bean configuration + * + * @param property The name of the property + * @param newValue The value + */ + void setPropertyValue(String property, Object newValue); + + /** + * Sets the BeanConfiguration as an Abstract bean definition + * @param isAbstract Whether its abstract or not + * @return This BeanConfiguration object + */ + BeanConfiguration setAbstract(boolean isAbstract); + + /** + * Sets the name of the parent bean. + * + * @param name Either a string which is the name of the bean, a RuntimeBeanReference or a BeanConfiguration + */ + void setParent(Object name); + + void setBeanDefinition(BeanDefinition definition); +} diff --git a/grails-spring/src/main/groovy/org/grails/spring/DefaultBeanConfiguration.java b/grails-spring/src/main/groovy/org/grails/spring/DefaultBeanConfiguration.java index c2ec0efa8a7..e0665defad3 100644 --- a/grails-spring/src/main/groovy/org/grails/spring/DefaultBeanConfiguration.java +++ b/grails-spring/src/main/groovy/org/grails/spring/DefaultBeanConfiguration.java @@ -1,317 +1,317 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.spring; - -import groovy.lang.GroovyObjectSupport; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.context.annotation.Lazy; -import org.springframework.util.Assert; - -/** - * Default implementation of the BeanConfiguration interface . - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - */ -public class DefaultBeanConfiguration extends GroovyObjectSupport implements BeanConfiguration { - - private static final String AUTOWIRE = "autowire"; - private static final String SINGLETON = "singleton"; - private static final String CONSTRUCTOR_ARGS = "constructorArgs"; - private static final String DESTROY_METHOD = "destroyMethod"; - private static final String FACTORY_BEAN = "factoryBean"; - private static final String FACTORY_METHOD = "factoryMethod"; - private static final String INIT_METHOD = "initMethod"; - private static final String BY_NAME = "byName"; - private static final String PARENT = "parent"; - private static final String BY_TYPE = "byType"; - private static final String BY_CONSTRUCTOR = "constructor"; - private static final List DYNAMIC_PROPS = Arrays.asList( - AUTOWIRE, - CONSTRUCTOR_ARGS, - DESTROY_METHOD, - FACTORY_BEAN, - FACTORY_METHOD, - INIT_METHOD, - BY_NAME, - BY_TYPE, - BY_CONSTRUCTOR); - - private String parentName; - - @Override - public Object getProperty(String property) { - @SuppressWarnings("unused") - AbstractBeanDefinition bd = getBeanDefinition(); - if (wrapper.isReadableProperty(property)) { - return wrapper.getPropertyValue(property); - } - if (DYNAMIC_PROPS.contains(property)) { - return null; - } - return super.getProperty(property); - } - - @Override - public void setProperty(String property, Object newValue) { - if (PARENT.equals(property)) { - setParent(newValue); - return; - } - - AbstractBeanDefinition bd = getBeanDefinition(); - if (AUTOWIRE.equals(property)) { - if (BY_NAME.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); - } - else if (BY_TYPE.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); - } - else if (Boolean.TRUE.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); - } - else if (BY_CONSTRUCTOR.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); - } - } - // constructorArgs - else if (CONSTRUCTOR_ARGS.equals(property) && newValue instanceof List) { - ConstructorArgumentValues cav = new ConstructorArgumentValues(); - for (Object e : (List)newValue) { - cav.addGenericArgumentValue(e); - } - bd.setConstructorArgumentValues(cav); - } - // destroyMethod - else if (DESTROY_METHOD.equals(property)) { - if (newValue != null) { - bd.setDestroyMethodName(newValue.toString()); - } - } - // factoryBean - else if (FACTORY_BEAN.equals(property)) { - if (newValue != null) { - bd.setFactoryBeanName(newValue.toString()); - } - } - // factoryMethod - else if (FACTORY_METHOD.equals(property)) { - if (newValue != null) { - bd.setFactoryMethodName(newValue.toString()); - } - } - // initMethod - else if (INIT_METHOD.equals(property)) { - if (newValue != null) { - bd.setInitMethodName(newValue.toString()); - } - } - // singleton property - else if(SINGLETON.equals(property)) { - bd.setScope(Boolean.TRUE.equals(newValue) ? BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE); - } - else if (wrapper.isWritableProperty(property)) { - wrapper.setPropertyValue(property, newValue); - } - // autowire - else { - super.setProperty(property, newValue); - } - } - - private Class clazz; - private String name; - private boolean singleton = true; - private AbstractBeanDefinition definition; - private Collection constructorArgs = Collections.emptyList(); - private BeanWrapper wrapper; - - public DefaultBeanConfiguration(String name, Class clazz) { - this.name = name; - this.clazz = clazz; - } - - public DefaultBeanConfiguration(String name, Class clazz, boolean prototype) { - this(name,clazz, Collections.emptyList()); - singleton = !prototype; - } - - public DefaultBeanConfiguration(String name) { - this(name,null, Collections.emptyList()); - } - - public DefaultBeanConfiguration(Class clazz2) { - clazz = clazz2; - } - - public DefaultBeanConfiguration(String name2, Class clazz2, Collection args) { - name = name2; - clazz = clazz2; - constructorArgs = args; - } - - public DefaultBeanConfiguration(String name2, boolean prototype) { - this(name2,null, Collections.emptyList()); - singleton = !prototype; - } - - public DefaultBeanConfiguration(Class clazz2, Collection constructorArguments) { - clazz = clazz2; - constructorArgs = constructorArguments; - } - - public String getName() { - return name; - } - - public boolean isSingleton() { - return singleton; - } - - public AbstractBeanDefinition getBeanDefinition() { - if (definition == null) { - definition = createBeanDefinition(); - } - return definition; - } - - public void setBeanDefinition(BeanDefinition definition) { - this.definition = (AbstractBeanDefinition)definition; - } - - protected AbstractBeanDefinition createBeanDefinition() { - AbstractBeanDefinition bd = new GenericBeanDefinition(); - if (!constructorArgs.isEmpty()) { - ConstructorArgumentValues cav = new ConstructorArgumentValues(); - for (Object constructorArg : constructorArgs) { - cav.addGenericArgumentValue(constructorArg); - } - bd.setConstructorArgumentValues(cav); - } - if(clazz != null) { - bd.setLazyInit( clazz.getAnnotation(Lazy.class) != null); - bd.setBeanClass(clazz); - } - bd.setScope(singleton ? AbstractBeanDefinition.SCOPE_SINGLETON : AbstractBeanDefinition.SCOPE_PROTOTYPE); - if (parentName != null) { - bd.setParentName(parentName); - } - wrapper = new BeanWrapperImpl(bd); - return bd; - } - - public BeanConfiguration addProperty(String propertyName, Object propertyValue) { - if (propertyValue instanceof BeanConfiguration) { - propertyValue = ((BeanConfiguration)propertyValue).getBeanDefinition(); - } - getBeanDefinition() - .getPropertyValues() - .addPropertyValue(propertyName,propertyValue); - - return this; - } - - public BeanConfiguration setDestroyMethod(String methodName) { - getBeanDefinition().setDestroyMethodName(methodName); - return this; - } - - public BeanConfiguration setDependsOn(String[] dependsOn) { - getBeanDefinition().setDependsOn(dependsOn); - return this; - } - - public BeanConfiguration setFactoryBean(String beanName) { - getBeanDefinition().setFactoryBeanName(beanName); - return this; - } - - public BeanConfiguration setFactoryMethod(String methodName) { - getBeanDefinition().setFactoryMethodName(methodName); - return this; - } - - public BeanConfiguration setAutowire(String type) { - if ("byName".equals(type)) { - getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); - } - else if ("byType".equals(type)) { - getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - } - return this; - } - - public void setName(String beanName) { - name = beanName; - } - - public Object getPropertyValue(String propName) { - PropertyValue propertyValue = getBeanDefinition() - .getPropertyValues() - .getPropertyValue(propName); - if (propertyValue == null) { - return null; - } - - return propertyValue.getValue(); - } - - public boolean hasProperty(String propName) { - return getBeanDefinition().getPropertyValues().contains(propName); - } - - public void setPropertyValue(String property, Object newValue) { - getBeanDefinition().getPropertyValues().addPropertyValue(property, newValue); - } - - public BeanConfiguration setAbstract(boolean isAbstract) { - getBeanDefinition().setAbstract(isAbstract); - return this; - } - - public void setParent(Object obj) { - Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference!"); - - if (obj instanceof String) { - parentName = (String)obj; - } - else if (obj instanceof RuntimeBeanReference) { - parentName = ((RuntimeBeanReference)obj).getBeanName(); - } - else if (obj instanceof BeanConfiguration) { - parentName = ((BeanConfiguration)obj).getName(); - } - getBeanDefinition().setParentName(parentName); - setAbstract(false); - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.spring; + +import groovy.lang.GroovyObjectSupport; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.Lazy; +import org.springframework.util.Assert; + +/** + * Default implementation of the BeanConfiguration interface . + * + * Credit must go to Solomon Duskis and the + * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring + * + * @author Graeme + * @since 0.3 + */ +public class DefaultBeanConfiguration extends GroovyObjectSupport implements BeanConfiguration { + + private static final String AUTOWIRE = "autowire"; + private static final String SINGLETON = "singleton"; + private static final String CONSTRUCTOR_ARGS = "constructorArgs"; + private static final String DESTROY_METHOD = "destroyMethod"; + private static final String FACTORY_BEAN = "factoryBean"; + private static final String FACTORY_METHOD = "factoryMethod"; + private static final String INIT_METHOD = "initMethod"; + private static final String BY_NAME = "byName"; + private static final String PARENT = "parent"; + private static final String BY_TYPE = "byType"; + private static final String BY_CONSTRUCTOR = "constructor"; + private static final List DYNAMIC_PROPS = Arrays.asList( + AUTOWIRE, + CONSTRUCTOR_ARGS, + DESTROY_METHOD, + FACTORY_BEAN, + FACTORY_METHOD, + INIT_METHOD, + BY_NAME, + BY_TYPE, + BY_CONSTRUCTOR); + + private String parentName; + + @Override + public Object getProperty(String property) { + @SuppressWarnings("unused") + AbstractBeanDefinition bd = getBeanDefinition(); + if (wrapper.isReadableProperty(property)) { + return wrapper.getPropertyValue(property); + } + if (DYNAMIC_PROPS.contains(property)) { + return null; + } + return super.getProperty(property); + } + + @Override + public void setProperty(String property, Object newValue) { + if (PARENT.equals(property)) { + setParent(newValue); + return; + } + + AbstractBeanDefinition bd = getBeanDefinition(); + if (AUTOWIRE.equals(property)) { + if (BY_NAME.equals(newValue)) { + bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); + } + else if (BY_TYPE.equals(newValue)) { + bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + } + else if (Boolean.TRUE.equals(newValue)) { + bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); + } + else if (BY_CONSTRUCTOR.equals(newValue)) { + bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); + } + } + // constructorArgs + else if (CONSTRUCTOR_ARGS.equals(property) && newValue instanceof List) { + ConstructorArgumentValues cav = new ConstructorArgumentValues(); + for (Object e : (List)newValue) { + cav.addGenericArgumentValue(e); + } + bd.setConstructorArgumentValues(cav); + } + // destroyMethod + else if (DESTROY_METHOD.equals(property)) { + if (newValue != null) { + bd.setDestroyMethodName(newValue.toString()); + } + } + // factoryBean + else if (FACTORY_BEAN.equals(property)) { + if (newValue != null) { + bd.setFactoryBeanName(newValue.toString()); + } + } + // factoryMethod + else if (FACTORY_METHOD.equals(property)) { + if (newValue != null) { + bd.setFactoryMethodName(newValue.toString()); + } + } + // initMethod + else if (INIT_METHOD.equals(property)) { + if (newValue != null) { + bd.setInitMethodName(newValue.toString()); + } + } + // singleton property + else if(SINGLETON.equals(property)) { + bd.setScope(Boolean.TRUE.equals(newValue) ? BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE); + } + else if (wrapper.isWritableProperty(property)) { + wrapper.setPropertyValue(property, newValue); + } + // autowire + else { + super.setProperty(property, newValue); + } + } + + private Class clazz; + private String name; + private boolean singleton = true; + private AbstractBeanDefinition definition; + private Collection constructorArgs = Collections.emptyList(); + private BeanWrapper wrapper; + + public DefaultBeanConfiguration(String name, Class clazz) { + this.name = name; + this.clazz = clazz; + } + + public DefaultBeanConfiguration(String name, Class clazz, boolean prototype) { + this(name,clazz, Collections.emptyList()); + singleton = !prototype; + } + + public DefaultBeanConfiguration(String name) { + this(name,null, Collections.emptyList()); + } + + public DefaultBeanConfiguration(Class clazz2) { + clazz = clazz2; + } + + public DefaultBeanConfiguration(String name2, Class clazz2, Collection args) { + name = name2; + clazz = clazz2; + constructorArgs = args; + } + + public DefaultBeanConfiguration(String name2, boolean prototype) { + this(name2,null, Collections.emptyList()); + singleton = !prototype; + } + + public DefaultBeanConfiguration(Class clazz2, Collection constructorArguments) { + clazz = clazz2; + constructorArgs = constructorArguments; + } + + public String getName() { + return name; + } + + public boolean isSingleton() { + return singleton; + } + + public AbstractBeanDefinition getBeanDefinition() { + if (definition == null) { + definition = createBeanDefinition(); + } + return definition; + } + + public void setBeanDefinition(BeanDefinition definition) { + this.definition = (AbstractBeanDefinition)definition; + } + + protected AbstractBeanDefinition createBeanDefinition() { + AbstractBeanDefinition bd = new GenericBeanDefinition(); + if (!constructorArgs.isEmpty()) { + ConstructorArgumentValues cav = new ConstructorArgumentValues(); + for (Object constructorArg : constructorArgs) { + cav.addGenericArgumentValue(constructorArg); + } + bd.setConstructorArgumentValues(cav); + } + if(clazz != null) { + bd.setLazyInit( clazz.getAnnotation(Lazy.class) != null); + bd.setBeanClass(clazz); + } + bd.setScope(singleton ? AbstractBeanDefinition.SCOPE_SINGLETON : AbstractBeanDefinition.SCOPE_PROTOTYPE); + if (parentName != null) { + bd.setParentName(parentName); + } + wrapper = new BeanWrapperImpl(bd); + return bd; + } + + public BeanConfiguration addProperty(String propertyName, Object propertyValue) { + if (propertyValue instanceof BeanConfiguration) { + propertyValue = ((BeanConfiguration)propertyValue).getBeanDefinition(); + } + getBeanDefinition() + .getPropertyValues() + .addPropertyValue(propertyName,propertyValue); + + return this; + } + + public BeanConfiguration setDestroyMethod(String methodName) { + getBeanDefinition().setDestroyMethodName(methodName); + return this; + } + + public BeanConfiguration setDependsOn(String[] dependsOn) { + getBeanDefinition().setDependsOn(dependsOn); + return this; + } + + public BeanConfiguration setFactoryBean(String beanName) { + getBeanDefinition().setFactoryBeanName(beanName); + return this; + } + + public BeanConfiguration setFactoryMethod(String methodName) { + getBeanDefinition().setFactoryMethodName(methodName); + return this; + } + + public BeanConfiguration setAutowire(String type) { + if ("byName".equals(type)) { + getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); + } + else if ("byType".equals(type)) { + getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + } + return this; + } + + public void setName(String beanName) { + name = beanName; + } + + public Object getPropertyValue(String propName) { + PropertyValue propertyValue = getBeanDefinition() + .getPropertyValues() + .getPropertyValue(propName); + if (propertyValue == null) { + return null; + } + + return propertyValue.getValue(); + } + + public boolean hasProperty(String propName) { + return getBeanDefinition().getPropertyValues().contains(propName); + } + + public void setPropertyValue(String property, Object newValue) { + getBeanDefinition().getPropertyValues().addPropertyValue(property, newValue); + } + + public BeanConfiguration setAbstract(boolean isAbstract) { + getBeanDefinition().setAbstract(isAbstract); + return this; + } + + public void setParent(Object obj) { + Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference!"); + + if (obj instanceof String) { + parentName = (String)obj; + } + else if (obj instanceof RuntimeBeanReference) { + parentName = ((RuntimeBeanReference)obj).getBeanName(); + } + else if (obj instanceof BeanConfiguration) { + parentName = ((BeanConfiguration)obj).getName(); + } + getBeanDefinition().setParentName(parentName); + setAbstract(false); + } +} diff --git a/grails-spring/src/main/groovy/org/grails/spring/DefaultRuntimeSpringConfiguration.java b/grails-spring/src/main/groovy/org/grails/spring/DefaultRuntimeSpringConfiguration.java index 7d430b2392d..8f97393e612 100644 --- a/grails-spring/src/main/groovy/org/grails/spring/DefaultRuntimeSpringConfiguration.java +++ b/grails-spring/src/main/groovy/org/grails/spring/DefaultRuntimeSpringConfiguration.java @@ -1,363 +1,363 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.spring; - -import groovy.lang.GroovySystem; -import groovy.lang.MetaClass; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.util.Assert; - -/** - * A programmable runtime Spring configuration that allows a spring ApplicationContext - * to be constructed at runtime. - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - */ -public class DefaultRuntimeSpringConfiguration implements RuntimeSpringConfiguration { - - private static final Log LOG = LogFactory.getLog(DefaultRuntimeSpringConfiguration.class); - protected GenericApplicationContext context; - private Map beanConfigs = new HashMap(); - private Map beanDefinitions = new HashMap(); - private Set beanNames = new LinkedHashSet(); - protected ApplicationContext parent; - protected ClassLoader classLoader; - protected Map> aliases = new HashMap>(); - protected ListableBeanFactory beanFactory; - - /** - * Creates the ApplicationContext instance. Subclasses can override to customise the used ApplicationContext - * - * @param parentCtx The parent ApplicationContext instance. Can be null. - * - * @return An instance of GenericApplicationContext - */ - protected GenericApplicationContext createApplicationContext(ApplicationContext parentCtx) { - if (parentCtx != null && beanFactory != null) { - Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory, - "ListableBeanFactory set must be a subclass of DefaultListableBeanFactory"); - - return new GrailsApplicationContext((DefaultListableBeanFactory) beanFactory,parentCtx); - } - - if (beanFactory != null) { - Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory, - "ListableBeanFactory set must be a subclass of DefaultListableBeanFactory"); - - return new GrailsApplicationContext((DefaultListableBeanFactory) beanFactory); - } - - if (parentCtx != null) { - return new GrailsApplicationContext(parentCtx); - } - - return new GrailsApplicationContext(); - } - - public DefaultRuntimeSpringConfiguration() { - super(); - } - - public DefaultRuntimeSpringConfiguration(ApplicationContext parent) { - this(parent, null); - } - - public DefaultRuntimeSpringConfiguration(ApplicationContext parent, ClassLoader cl) { - this.parent = parent; - classLoader = cl; - } - - private void trySettingClassLoaderOnContextIfFoundInParent(ApplicationContext parentCtx) { - if (parentCtx.containsBean("classLoader")) { - Object cl = parentCtx.getBean("classLoader"); - if (cl instanceof ClassLoader) { - setClassLoaderOnContext((ClassLoader)cl); - } - } - } - - private void setClassLoaderOnContext(ClassLoader cl) { - context.setClassLoader(cl); - context.getBeanFactory().setBeanClassLoader(cl); - } - - /** - * Initialises the ApplicationContext instance. - */ - protected void initialiseApplicationContext() { - if (context != null) { - return; - } - - context = createApplicationContext(parent); - - if (parent != null && classLoader == null) { - trySettingClassLoaderOnContextIfFoundInParent(parent); - } - else if (classLoader != null) { - setClassLoaderOnContext(classLoader); - } - - Assert.notNull(context, "ApplicationContext cannot be null"); - } - - public BeanConfiguration addSingletonBean(String name, @SuppressWarnings("rawtypes") Class clazz) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration addPrototypeBean(String name, @SuppressWarnings("rawtypes") Class clazz) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,true); - registerBeanConfiguration(name, bc); - return bc; - } - - public ApplicationContext getApplicationContext() { - long now = LOG.isDebugEnabled() ? System.currentTimeMillis() : 0; - initialiseApplicationContext(); - registerBeansWithContext(context); - context.refresh(); - if (LOG.isDebugEnabled()) { - LOG.debug("Created ApplicationContext in " + (System.currentTimeMillis() - now) + "ms"); - } - return context; - } - - public ApplicationContext getUnrefreshedApplicationContext() { - initialiseApplicationContext(); - return context; - } - - public BeanConfiguration addSingletonBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration createSingletonBean(@SuppressWarnings("rawtypes") Class clazz) { - return new DefaultBeanConfiguration(clazz); - } - - @SuppressWarnings("rawtypes") - public BeanConfiguration addSingletonBean(String name, Class clazz, Collection args) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,args); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration addPrototypeBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,true); - registerBeanConfiguration(name, bc); - return bc; - } - - private void registerBeanConfiguration(String name, BeanConfiguration bc) { - beanConfigs.put(name,bc); - beanNames.add(name); - } - - @SuppressWarnings("rawtypes") - public BeanConfiguration createSingletonBean(Class clazz, Collection constructorArguments) { - return new DefaultBeanConfiguration(clazz, constructorArguments); - } - - public BeanConfiguration createPrototypeBean(String name) { - return new DefaultBeanConfiguration(name,true); - } - - public BeanConfiguration createSingletonBean(String name) { - return new DefaultBeanConfiguration(name); - } - - public void addBeanConfiguration(String beanName, BeanConfiguration beanConfiguration) { - beanConfiguration.setName(beanName); - registerBeanConfiguration(beanName, beanConfiguration); - } - - public void addBeanDefinition(String name, BeanDefinition bd) { - beanDefinitions.put(name,bd); - beanConfigs.remove(name); - beanNames.add(name); - } - - public boolean containsBean(String name) { - return beanNames.contains(name); - } - - public BeanConfiguration getBeanConfig(String name) { - return beanConfigs.get(name); - } - - public AbstractBeanDefinition createBeanDefinition(String name) { - if (containsBean(name)) { - if (beanDefinitions.containsKey(name)) { - return (AbstractBeanDefinition)beanDefinitions.get(name); - } - if (beanConfigs.containsKey(name)) { - return beanConfigs.get(name).getBeanDefinition(); - } - } - return null; - } - - public void registerPostProcessor(BeanFactoryPostProcessor processor) { - initialiseApplicationContext(); - context.addBeanFactoryPostProcessor(processor); - } - - public List getBeanNames() { - return Collections.unmodifiableList(new ArrayList(beanNames)); - } - - public void registerBeansWithContext(GenericApplicationContext applicationContext) { - registerBeansWithRegistry(applicationContext); - } - - public void registerBeansWithRegistry(BeanDefinitionRegistry registry) { - registerUnrefreshedBeansWithRegistry(registry); - registerBeanConfigsWithRegistry(registry); - registerBeanDefinitionsWithRegistry(registry); - registerBeanAliasesWithRegistry(registry); - } - - private void registerUnrefreshedBeansWithRegistry(BeanDefinitionRegistry registry) { - if (context != null) { - for (String beanName : context.getBeanDefinitionNames()) { - registry.registerBeanDefinition(beanName, context.getBeanDefinition(beanName)); - } - } - } - - private void registerBeanConfigsWithRegistry(BeanDefinitionRegistry registry) { - for (BeanConfiguration bc : beanConfigs.values()) { - String beanName = bc.getName(); - if (LOG.isDebugEnabled()) { - LOG.debug("[RuntimeConfiguration] Registering bean [" + beanName + "]"); - if (LOG.isTraceEnabled()) { - PropertyValue[] pvs = bc.getBeanDefinition() - .getPropertyValues() - .getPropertyValues(); - for (PropertyValue pv : pvs) { - LOG.trace("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); - } - } - } - - registry.registerBeanDefinition(beanName, bc.getBeanDefinition()); - } - } - - private void registerBeanDefinitionsWithRegistry(BeanDefinitionRegistry registry) { - for (Object key : beanDefinitions.keySet()) { - BeanDefinition bd = beanDefinitions.get(key); - if (LOG.isDebugEnabled()) { - LOG.debug("[RuntimeConfiguration] Registering bean [" + key + "]"); - if (LOG.isTraceEnabled()) { - PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues(); - for (PropertyValue pv : pvs) { - LOG.trace("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); - } - } - } - final String beanName = key.toString(); - registry.registerBeanDefinition(beanName, bd); - } - } - - public void registerBeansWithConfig(RuntimeSpringConfiguration targetSpringConfig) { - if (targetSpringConfig == null) { - return; - } - - ApplicationContext ctx = targetSpringConfig.getUnrefreshedApplicationContext(); - if (ctx instanceof BeanDefinitionRegistry) { - final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) ctx; - registerUnrefreshedBeansWithRegistry(registry); - registerBeansWithRegistry(registry); - } - for (Map.Entry beanEntry : beanConfigs.entrySet()) { - targetSpringConfig.addBeanConfiguration(beanEntry.getKey(), beanEntry.getValue()); - } - } - - private void registerBeanAliasesWithRegistry(BeanDefinitionRegistry beanDefinitionRegistry) { - for (Map.Entry> entry : aliases.entrySet()) { - String beanName = entry.getKey(); - List beanAliases = entry.getValue(); - if (beanAliases != null && !beanAliases.isEmpty()) { - for (String alias : beanAliases) { - beanDefinitionRegistry.registerAlias(beanName, alias); - } - } - } - } - - private void removeBeanDefinition(BeanDefinitionRegistry registry, String beanName) { - MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(registry.getClass()); - if (!mc.respondsTo(registry, "removeBeanDefinition").isEmpty()) { - mc.invokeMethod(registry, "removeBeanDefinition", new Object[] { beanName }); - } - } - - public BeanConfiguration addAbstractBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name); - bc.setAbstract(true); - registerBeanConfiguration(name, bc); - return bc; - } - - public void addAlias(String alias, String beanName) { - List beanAliases = aliases.get(beanName); - if (beanAliases == null) { - beanAliases = new ArrayList(); - aliases.put(beanName, beanAliases); - } - beanAliases.add(alias); - } - - public BeanDefinition getBeanDefinition(String beanName) { - return beanDefinitions.get(beanName); - } - - public void setBeanFactory(ListableBeanFactory beanFactory) { - this.beanFactory = beanFactory; - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.spring; + +import groovy.lang.GroovySystem; +import groovy.lang.MetaClass; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.Assert; + +/** + * A programmable runtime Spring configuration that allows a spring ApplicationContext + * to be constructed at runtime. + * + * Credit must go to Solomon Duskis and the + * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring + * + * @author Graeme + * @since 0.3 + */ +public class DefaultRuntimeSpringConfiguration implements RuntimeSpringConfiguration { + + private static final Log LOG = LogFactory.getLog(DefaultRuntimeSpringConfiguration.class); + protected GenericApplicationContext context; + private Map beanConfigs = new HashMap(); + private Map beanDefinitions = new HashMap(); + private Set beanNames = new LinkedHashSet(); + protected ApplicationContext parent; + protected ClassLoader classLoader; + protected Map> aliases = new HashMap>(); + protected ListableBeanFactory beanFactory; + + /** + * Creates the ApplicationContext instance. Subclasses can override to customise the used ApplicationContext + * + * @param parentCtx The parent ApplicationContext instance. Can be null. + * + * @return An instance of GenericApplicationContext + */ + protected GenericApplicationContext createApplicationContext(ApplicationContext parentCtx) { + if (parentCtx != null && beanFactory != null) { + Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory, + "ListableBeanFactory set must be a subclass of DefaultListableBeanFactory"); + + return new GrailsApplicationContext((DefaultListableBeanFactory) beanFactory,parentCtx); + } + + if (beanFactory != null) { + Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory, + "ListableBeanFactory set must be a subclass of DefaultListableBeanFactory"); + + return new GrailsApplicationContext((DefaultListableBeanFactory) beanFactory); + } + + if (parentCtx != null) { + return new GrailsApplicationContext(parentCtx); + } + + return new GrailsApplicationContext(); + } + + public DefaultRuntimeSpringConfiguration() { + super(); + } + + public DefaultRuntimeSpringConfiguration(ApplicationContext parent) { + this(parent, null); + } + + public DefaultRuntimeSpringConfiguration(ApplicationContext parent, ClassLoader cl) { + this.parent = parent; + classLoader = cl; + } + + private void trySettingClassLoaderOnContextIfFoundInParent(ApplicationContext parentCtx) { + if (parentCtx.containsBean("classLoader")) { + Object cl = parentCtx.getBean("classLoader"); + if (cl instanceof ClassLoader) { + setClassLoaderOnContext((ClassLoader)cl); + } + } + } + + private void setClassLoaderOnContext(ClassLoader cl) { + context.setClassLoader(cl); + context.getBeanFactory().setBeanClassLoader(cl); + } + + /** + * Initialises the ApplicationContext instance. + */ + protected void initialiseApplicationContext() { + if (context != null) { + return; + } + + context = createApplicationContext(parent); + + if (parent != null && classLoader == null) { + trySettingClassLoaderOnContextIfFoundInParent(parent); + } + else if (classLoader != null) { + setClassLoaderOnContext(classLoader); + } + + Assert.notNull(context, "ApplicationContext cannot be null"); + } + + public BeanConfiguration addSingletonBean(String name, @SuppressWarnings("rawtypes") Class clazz) { + BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz); + registerBeanConfiguration(name, bc); + return bc; + } + + public BeanConfiguration addPrototypeBean(String name, @SuppressWarnings("rawtypes") Class clazz) { + BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,true); + registerBeanConfiguration(name, bc); + return bc; + } + + public ApplicationContext getApplicationContext() { + long now = LOG.isDebugEnabled() ? System.currentTimeMillis() : 0; + initialiseApplicationContext(); + registerBeansWithContext(context); + context.refresh(); + if (LOG.isDebugEnabled()) { + LOG.debug("Created ApplicationContext in " + (System.currentTimeMillis() - now) + "ms"); + } + return context; + } + + public ApplicationContext getUnrefreshedApplicationContext() { + initialiseApplicationContext(); + return context; + } + + public BeanConfiguration addSingletonBean(String name) { + BeanConfiguration bc = new DefaultBeanConfiguration(name); + registerBeanConfiguration(name, bc); + return bc; + } + + public BeanConfiguration createSingletonBean(@SuppressWarnings("rawtypes") Class clazz) { + return new DefaultBeanConfiguration(clazz); + } + + @SuppressWarnings("rawtypes") + public BeanConfiguration addSingletonBean(String name, Class clazz, Collection args) { + BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,args); + registerBeanConfiguration(name, bc); + return bc; + } + + public BeanConfiguration addPrototypeBean(String name) { + BeanConfiguration bc = new DefaultBeanConfiguration(name,true); + registerBeanConfiguration(name, bc); + return bc; + } + + private void registerBeanConfiguration(String name, BeanConfiguration bc) { + beanConfigs.put(name,bc); + beanNames.add(name); + } + + @SuppressWarnings("rawtypes") + public BeanConfiguration createSingletonBean(Class clazz, Collection constructorArguments) { + return new DefaultBeanConfiguration(clazz, constructorArguments); + } + + public BeanConfiguration createPrototypeBean(String name) { + return new DefaultBeanConfiguration(name,true); + } + + public BeanConfiguration createSingletonBean(String name) { + return new DefaultBeanConfiguration(name); + } + + public void addBeanConfiguration(String beanName, BeanConfiguration beanConfiguration) { + beanConfiguration.setName(beanName); + registerBeanConfiguration(beanName, beanConfiguration); + } + + public void addBeanDefinition(String name, BeanDefinition bd) { + beanDefinitions.put(name,bd); + beanConfigs.remove(name); + beanNames.add(name); + } + + public boolean containsBean(String name) { + return beanNames.contains(name); + } + + public BeanConfiguration getBeanConfig(String name) { + return beanConfigs.get(name); + } + + public AbstractBeanDefinition createBeanDefinition(String name) { + if (containsBean(name)) { + if (beanDefinitions.containsKey(name)) { + return (AbstractBeanDefinition)beanDefinitions.get(name); + } + if (beanConfigs.containsKey(name)) { + return beanConfigs.get(name).getBeanDefinition(); + } + } + return null; + } + + public void registerPostProcessor(BeanFactoryPostProcessor processor) { + initialiseApplicationContext(); + context.addBeanFactoryPostProcessor(processor); + } + + public List getBeanNames() { + return Collections.unmodifiableList(new ArrayList(beanNames)); + } + + public void registerBeansWithContext(GenericApplicationContext applicationContext) { + registerBeansWithRegistry(applicationContext); + } + + public void registerBeansWithRegistry(BeanDefinitionRegistry registry) { + registerUnrefreshedBeansWithRegistry(registry); + registerBeanConfigsWithRegistry(registry); + registerBeanDefinitionsWithRegistry(registry); + registerBeanAliasesWithRegistry(registry); + } + + private void registerUnrefreshedBeansWithRegistry(BeanDefinitionRegistry registry) { + if (context != null) { + for (String beanName : context.getBeanDefinitionNames()) { + registry.registerBeanDefinition(beanName, context.getBeanDefinition(beanName)); + } + } + } + + private void registerBeanConfigsWithRegistry(BeanDefinitionRegistry registry) { + for (BeanConfiguration bc : beanConfigs.values()) { + String beanName = bc.getName(); + if (LOG.isDebugEnabled()) { + LOG.debug("[RuntimeConfiguration] Registering bean [" + beanName + "]"); + if (LOG.isTraceEnabled()) { + PropertyValue[] pvs = bc.getBeanDefinition() + .getPropertyValues() + .getPropertyValues(); + for (PropertyValue pv : pvs) { + LOG.trace("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); + } + } + } + + registry.registerBeanDefinition(beanName, bc.getBeanDefinition()); + } + } + + private void registerBeanDefinitionsWithRegistry(BeanDefinitionRegistry registry) { + for (Object key : beanDefinitions.keySet()) { + BeanDefinition bd = beanDefinitions.get(key); + if (LOG.isDebugEnabled()) { + LOG.debug("[RuntimeConfiguration] Registering bean [" + key + "]"); + if (LOG.isTraceEnabled()) { + PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues(); + for (PropertyValue pv : pvs) { + LOG.trace("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); + } + } + } + final String beanName = key.toString(); + registry.registerBeanDefinition(beanName, bd); + } + } + + public void registerBeansWithConfig(RuntimeSpringConfiguration targetSpringConfig) { + if (targetSpringConfig == null) { + return; + } + + ApplicationContext ctx = targetSpringConfig.getUnrefreshedApplicationContext(); + if (ctx instanceof BeanDefinitionRegistry) { + final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) ctx; + registerUnrefreshedBeansWithRegistry(registry); + registerBeansWithRegistry(registry); + } + for (Map.Entry beanEntry : beanConfigs.entrySet()) { + targetSpringConfig.addBeanConfiguration(beanEntry.getKey(), beanEntry.getValue()); + } + } + + private void registerBeanAliasesWithRegistry(BeanDefinitionRegistry beanDefinitionRegistry) { + for (Map.Entry> entry : aliases.entrySet()) { + String beanName = entry.getKey(); + List beanAliases = entry.getValue(); + if (beanAliases != null && !beanAliases.isEmpty()) { + for (String alias : beanAliases) { + beanDefinitionRegistry.registerAlias(beanName, alias); + } + } + } + } + + private void removeBeanDefinition(BeanDefinitionRegistry registry, String beanName) { + MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(registry.getClass()); + if (!mc.respondsTo(registry, "removeBeanDefinition").isEmpty()) { + mc.invokeMethod(registry, "removeBeanDefinition", new Object[] { beanName }); + } + } + + public BeanConfiguration addAbstractBean(String name) { + BeanConfiguration bc = new DefaultBeanConfiguration(name); + bc.setAbstract(true); + registerBeanConfiguration(name, bc); + return bc; + } + + public void addAlias(String alias, String beanName) { + List beanAliases = aliases.get(beanName); + if (beanAliases == null) { + beanAliases = new ArrayList(); + aliases.put(beanName, beanAliases); + } + beanAliases.add(alias); + } + + public BeanDefinition getBeanDefinition(String beanName) { + return beanDefinitions.get(beanName); + } + + public void setBeanFactory(ListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } +} diff --git a/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyHandlerInterceptor.java b/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyHandlerInterceptor.java index 6ae1d5f388c..eee8b49e33d 100755 --- a/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyHandlerInterceptor.java +++ b/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyHandlerInterceptor.java @@ -1,37 +1,37 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.web.plugins.support; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -public class MyHandlerInterceptor implements HandlerInterceptor { - - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - // do nothing - } - - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { - // do nothing - } - - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - return false; - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.web.plugins.support; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +public class MyHandlerInterceptor implements HandlerInterceptor { + + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + // do nothing + } + + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { + // do nothing + } + + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + return false; + } +} diff --git a/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyWebRequestInterceptor.java b/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyWebRequestInterceptor.java index 1323babe65b..c8b6fb7a9c9 100755 --- a/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyWebRequestInterceptor.java +++ b/grails-test-suite-uber/src/test/groovy/org/grails/web/plugins/support/MyWebRequestInterceptor.java @@ -1,35 +1,35 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.web.plugins.support; - -import org.springframework.ui.ModelMap; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.context.request.WebRequestInterceptor; - -public class MyWebRequestInterceptor implements WebRequestInterceptor { - - public void afterCompletion(WebRequest request, Exception ex) { - // do nothing - } - - public void postHandle(WebRequest request, ModelMap model) { - // do nothing - } - - public void preHandle(WebRequest request) throws Exception { - // do nothing - } -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.web.plugins.support; + +import org.springframework.ui.ModelMap; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.WebRequestInterceptor; + +public class MyWebRequestInterceptor implements WebRequestInterceptor { + + public void afterCompletion(WebRequest request, Exception ex) { + // do nothing + } + + public void postHandle(WebRequest request, ModelMap model) { + // do nothing + } + + public void preHandle(WebRequest request) throws Exception { + // do nothing + } +} diff --git a/grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy b/grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy index e805191f207..593159cd753 100644 --- a/grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy +++ b/grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy @@ -1,193 +1,193 @@ -/* - * Copyright 2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.web.servlet - -import grails.artefact.Artefact -import grails.testing.web.controllers.ControllerUnitTest -import spock.lang.Specification - -/** - * Tests for the bindData method - * - */ -class BindDataMethodTests extends Specification implements ControllerUnitTest { - - void 'Test bindData with Map'() { - when: - def model = controller.bindWithMap() - def target = model.target - - then: - target.name == 'Marc Palmer' - } - - void 'Test bindData With Excludes'() { - when: - def model = controller.bindWithExcludes() - def target = model.target - - then: - target.name == 'Marc Palmer' - target.email == null - } - - void 'Test bindData With Includes'() { - when: - def model = controller.bindWithIncludes() - def target = model.target - - then: - target.name == 'Marc Palmer' - target.email == null - } - - void 'Test bindData With Empty Includes/Excludes Map'() { - when: - def model = controller.bindWithEmptyIncludesExcludesMap() - def target = model.target - - then: - target.name == 'Marc Palmer' - target.email == 'dowantthis' - } - - void 'Test bindData Overriding Included With Excluded'() { - when: - def model = controller.bindWithIncludeOverriddenByExclude() - def target = model.target - - then: - target.name == 'Marc Palmer' - target.email == null - } - - void 'Test bindData With Prefix Filter'() { - when: - def model = controller.bindWithPrefixFilter() - def target = model.target - - then: - target.name == 'Lee Butts' - target.email == 'lee@mail.com' - } - - void 'Test bindData With Disallowed And GrailsParameterMap'() { - when: - params.name = 'Marc Palmer' - params.email = 'dontwantthis' - params.'address.country' = 'gbr' - def model = controller.bindWithParamsAndDisallowed() - def target = model.target - - then: - target.name == 'Marc Palmer' - target.address.country == 'gbr' - target.email == null - } - - void 'Test bindData With Prefix Filter And Disallowed'() { - when: - def model = controller.bindWithPrefixFilterAndDisallowed() - def target = model.target - - then: - target.name == 'Lee Butts' - target.email == null - } - - void 'Test bindData Converts Single String In Map To List'() { - when: - def model = controller.bindWithStringConvertedToList() - def target = model.target - - then: - target.name == 'Lee Butts' - target.email == null - } -} - -@Artefact('Controller') -class BindingController { - - def bindWithMap() { - def target = new CommandObject() - bindData target, [ name : 'Marc Palmer' ] - [target: target] - } - - def bindWithExcludes() { - def target = new CommandObject() - bindData target, [name: 'Marc Palmer', email: 'dontwantthis'], [exclude: ['email']] - [target: target] - } - - def bindWithIncludes() { - def target = new CommandObject() - bindData target, [ name : 'Marc Palmer', email : 'dontwantthis' ], [include:['name']] - [target: target] - } - - def bindWithEmptyIncludesExcludesMap() { - def target = new CommandObject() - bindData target, [ name : 'Marc Palmer', email : 'dowantthis' ], [:] - [target: target] - } - - def bindWithIncludeOverriddenByExclude() { - def target = new CommandObject() - bindData target, [ name : 'Marc Palmer', email : 'dontwantthis' ], [include: ['name', 'email'], exclude: ['email']] - [target: target] - } - - def bindWithPrefixFilter() { - def target = new CommandObject() - def filter = "lee" - bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], filter - [target: target] - } - - def bindWithParamsAndDisallowed() { - def target = new CommandObject() - bindData target, params, [exclude:['email']] - [target: target] - } - - def bindWithPrefixFilterAndDisallowed() { - def target = new CommandObject() - def filter = "lee" - def disallowed = [exclude:["email"]] - bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], disallowed, filter - [target: target] - } - - def bindWithStringConvertedToList() { - def target = new CommandObject() - def filter = "lee" - def disallowed = [exclude:"email"] - bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], disallowed, filter - [target: target] - } -} - -class CommandObject { - String name - String email - Address address = new Address() -} - -class Address { - String country -} +/* + * Copyright 2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.web.servlet + +import grails.artefact.Artefact +import grails.testing.web.controllers.ControllerUnitTest +import spock.lang.Specification + +/** + * Tests for the bindData method + * + */ +class BindDataMethodTests extends Specification implements ControllerUnitTest { + + void 'Test bindData with Map'() { + when: + def model = controller.bindWithMap() + def target = model.target + + then: + target.name == 'Marc Palmer' + } + + void 'Test bindData With Excludes'() { + when: + def model = controller.bindWithExcludes() + def target = model.target + + then: + target.name == 'Marc Palmer' + target.email == null + } + + void 'Test bindData With Includes'() { + when: + def model = controller.bindWithIncludes() + def target = model.target + + then: + target.name == 'Marc Palmer' + target.email == null + } + + void 'Test bindData With Empty Includes/Excludes Map'() { + when: + def model = controller.bindWithEmptyIncludesExcludesMap() + def target = model.target + + then: + target.name == 'Marc Palmer' + target.email == 'dowantthis' + } + + void 'Test bindData Overriding Included With Excluded'() { + when: + def model = controller.bindWithIncludeOverriddenByExclude() + def target = model.target + + then: + target.name == 'Marc Palmer' + target.email == null + } + + void 'Test bindData With Prefix Filter'() { + when: + def model = controller.bindWithPrefixFilter() + def target = model.target + + then: + target.name == 'Lee Butts' + target.email == 'lee@mail.com' + } + + void 'Test bindData With Disallowed And GrailsParameterMap'() { + when: + params.name = 'Marc Palmer' + params.email = 'dontwantthis' + params.'address.country' = 'gbr' + def model = controller.bindWithParamsAndDisallowed() + def target = model.target + + then: + target.name == 'Marc Palmer' + target.address.country == 'gbr' + target.email == null + } + + void 'Test bindData With Prefix Filter And Disallowed'() { + when: + def model = controller.bindWithPrefixFilterAndDisallowed() + def target = model.target + + then: + target.name == 'Lee Butts' + target.email == null + } + + void 'Test bindData Converts Single String In Map To List'() { + when: + def model = controller.bindWithStringConvertedToList() + def target = model.target + + then: + target.name == 'Lee Butts' + target.email == null + } +} + +@Artefact('Controller') +class BindingController { + + def bindWithMap() { + def target = new CommandObject() + bindData target, [ name : 'Marc Palmer' ] + [target: target] + } + + def bindWithExcludes() { + def target = new CommandObject() + bindData target, [name: 'Marc Palmer', email: 'dontwantthis'], [exclude: ['email']] + [target: target] + } + + def bindWithIncludes() { + def target = new CommandObject() + bindData target, [ name : 'Marc Palmer', email : 'dontwantthis' ], [include:['name']] + [target: target] + } + + def bindWithEmptyIncludesExcludesMap() { + def target = new CommandObject() + bindData target, [ name : 'Marc Palmer', email : 'dowantthis' ], [:] + [target: target] + } + + def bindWithIncludeOverriddenByExclude() { + def target = new CommandObject() + bindData target, [ name : 'Marc Palmer', email : 'dontwantthis' ], [include: ['name', 'email'], exclude: ['email']] + [target: target] + } + + def bindWithPrefixFilter() { + def target = new CommandObject() + def filter = "lee" + bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], filter + [target: target] + } + + def bindWithParamsAndDisallowed() { + def target = new CommandObject() + bindData target, params, [exclude:['email']] + [target: target] + } + + def bindWithPrefixFilterAndDisallowed() { + def target = new CommandObject() + def filter = "lee" + def disallowed = [exclude:["email"]] + bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], disallowed, filter + [target: target] + } + + def bindWithStringConvertedToList() { + def target = new CommandObject() + def filter = "lee" + def disallowed = [exclude:"email"] + bindData target, [ 'mark.name' : 'Marc Palmer', 'mark.email' : 'dontwantthis', 'lee.name': 'Lee Butts', 'lee.email': 'lee@mail.com'], disallowed, filter + [target: target] + } +} + +class CommandObject { + String name + String email + Address address = new Address() +} + +class Address { + String country +}