001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.io.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.Serializable;
023import java.nio.charset.Charset;
024import java.nio.charset.StandardCharsets;
025import java.time.Instant;
026import java.util.Locale;
027import java.util.Objects;
028import javax.servlet.http.HttpServletResponse;
029import org.apache.wicket.Application;
030import org.apache.wicket.IWicketInternalException;
031import org.apache.wicket.Session;
032import org.apache.wicket.WicketRuntimeException;
033import org.apache.wicket.core.util.lang.WicketObjects;
034import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
035import org.apache.wicket.javascript.IJavaScriptCompressor;
036import org.apache.wicket.markup.html.IPackageResourceGuard;
037import org.apache.wicket.mock.MockWebRequest;
038import org.apache.wicket.request.Url;
039import org.apache.wicket.request.cycle.RequestCycle;
040import org.apache.wicket.request.resource.caching.IStaticCacheableResource;
041import org.apache.wicket.resource.IScopeAwareTextResourceProcessor;
042import org.apache.wicket.resource.ITextResourceCompressor;
043import org.apache.wicket.response.StringResponse;
044import org.apache.wicket.util.io.IOUtils;
045import org.apache.wicket.util.lang.Classes;
046import org.apache.wicket.util.lang.Packages;
047import org.apache.wicket.util.resource.IFixedLocationResourceStream;
048import org.apache.wicket.util.resource.IResourceStream;
049import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
050import org.apache.wicket.util.resource.ResourceStreamWrapper;
051import org.apache.wicket.util.string.Strings;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055/**
056 * Represents a localizable static resource.
057 * <p>
058 * Use like eg:
059 * 
060 * <pre>
061 * MyPackageResource IMG_UNKNOWN = new MyPackageResource(EditPage.class, &quot;questionmark.gif&quot;);
062 * </pre>
063 * 
064 * where the static resource references image 'questionmark.gif' from the the package that EditPage
065 * is in to get a package resource.
066 * </p>
067 * 
068 * Access to resources can be granted or denied via a {@link IPackageResourceGuard}. Please see
069 * {@link org.apache.wicket.settings.ResourceSettings#getPackageResourceGuard()} as well.
070 * 
071 * @author Jonathan Locke
072 * @author Eelco Hillenius
073 * @author Juergen Donnerstag
074 * @author Matej Knopp
075 * @author Tobias Soloschenko
076 */
077public class PackageResource extends AbstractResource implements IStaticCacheableResource
078{
079        private static final Logger log = LoggerFactory.getLogger(PackageResource.class);
080
081        private static final long serialVersionUID = 1L;
082
083        /**
084         * Exception thrown when the creation of a package resource is not allowed.
085         */
086        public static final class PackageResourceBlockedException extends WicketRuntimeException
087                implements
088                        IWicketInternalException
089        {
090                private static final long serialVersionUID = 1L;
091
092                /**
093                 * Construct.
094                 * 
095                 * @param message
096                 *            error message
097                 */
098                public PackageResourceBlockedException(String message)
099                {
100                        super(message);
101                }
102        }
103
104        /**
105         * The path to the resource
106         */
107        private final String absolutePath;
108
109        /**
110         * The resource's locale
111         */
112        private final Locale locale;
113
114        /**
115         * The path this resource was created with.
116         */
117        private final String path;
118
119        /**
120         * The scoping class, used for class loading and to determine the package.
121         */
122        private final String scopeName;
123
124        /**
125         * The name of the resource
126         */
127        private final String name;
128
129        /**
130         * The resource's style
131         */
132        private final String style;
133
134        /**
135         * The component's variation (of the style)
136         */
137        private final String variation;
138
139        /**
140         * A flag indicating whether {@code ITextResourceCompressor} can be used to compress this
141         * resource. Default is {@code false} because this resource may be used for binary data (e.g. an
142         * image). Specializations of this class should change this flag appropriately.
143         */
144        private boolean compress = false;
145
146        /**
147         * controls whether {@link org.apache.wicket.request.resource.caching.IResourceCachingStrategy}
148         * should be applied to resource
149         */
150        private boolean cachingEnabled = true;
151
152        /**
153         * text encoding (may be null) - only makes sense for character-based resources
154         */
155        private String textEncoding = null;
156
157        /**
158         * Reads the resource buffered - the content is copied into memory
159         */
160        private boolean readBuffered = true;
161
162        /**
163         * Hidden constructor.
164         * 
165         * @param scope
166         *            This argument will be used to get the class loader for loading the package
167         *            resource, and to determine what package it is in
168         * @param name
169         *            The relative path to the resource
170         * @param locale
171         *            The locale of the resource
172         * @param style
173         *            The style of the resource
174         * @param variation
175         *            The component's variation (of the style)
176         */
177        protected PackageResource(final Class<?> scope, final String name, final Locale locale,
178                final String style, final String variation)
179        {
180                // Convert resource path to absolute path relative to base package
181                absolutePath = Packages.absolutePath(scope, name);
182
183                final String parentEscape = getParentFolderPlaceholder();
184
185                if (Strings.isEmpty(parentEscape) == false)
186                {
187                        path = Strings.replaceAll(name, "../", parentEscape + "/").toString();
188                }
189                else
190                {
191                        path = name;
192                }
193
194                this.name = name;
195                this.scopeName = scope.getName();
196                this.locale = locale;
197                this.style = style;
198                this.variation = variation;
199        }
200
201        private Locale getCurrentLocale()
202        {
203                if (locale == null && Session.exists())
204                {
205                        return Session.get().getLocale();
206                }
207
208                return locale;
209        }
210
211        private String getCurrentStyle()
212        {
213                if (style == null && Session.exists())
214                {
215                        return Session.get().getStyle();
216                }
217
218                return style;
219        }
220
221        /**
222         * Returns true if the caching for this resource is enabled
223         * 
224         * @return if the caching is enabled
225         */
226        @Override
227        public boolean isCachingEnabled()
228        {
229                return cachingEnabled;
230        }
231
232        /**
233         * Sets the caching for this resource to be enabled
234         * 
235         * @param enabled
236         *            if the cacheing should be enabled
237         */
238        public void setCachingEnabled(final boolean enabled)
239        {
240                this.cachingEnabled = enabled;
241        }
242
243        /**
244         * get text encoding (intented for character-based resources)
245         *
246         * @return custom encoding or {@code null} to use default
247         */
248        public String getTextEncoding()
249        {
250                return textEncoding;
251        }
252
253        /**
254         * set text encoding (intented for character-based resources)
255         *
256         * @param textEncoding
257         *            custom encoding or {@code null} to use default
258         */
259        public void setTextEncoding(final String textEncoding)
260        {
261                this.textEncoding = textEncoding;
262        }
263
264        @Override
265        public Serializable getCacheKey()
266        {
267                Class<?> scope = getScope();
268                String currentStyle = getCurrentStyle();
269                Locale currentLocale = getCurrentLocale();
270                
271                 IResourceStream packageResource = Application.get()
272                        .getResourceSettings()
273                        .getResourceStreamLocator()
274                        .locate(scope, absolutePath, currentStyle, variation, currentLocale, null, false);
275                 
276                // if resource stream can not be found do not cache
277                if (packageResource != null)
278                {
279                        return new CacheKey(scopeName, absolutePath, currentLocale, currentStyle, variation);
280                }
281
282                return null;
283        }
284
285        /**
286         * Gets the scoping class, used for class loading and to determine the package.
287         * 
288         * @return the scoping class
289         */
290        public final Class<?> getScope()
291        {
292                return WicketObjects.resolveClass(scopeName);
293        }
294
295        public final String getName()
296        {
297                return name;
298        }
299
300        /**
301         * Gets the style.
302         * 
303         * @return the style
304         */
305        public final String getStyle()
306        {
307                return style;
308        }
309
310        /**
311         * creates a new resource response based on the request attributes
312         * 
313         * @param attributes
314         *            current request attributes from client
315         * @return resource response for answering request
316         */
317        @Override
318        protected ResourceResponse newResourceResponse(Attributes attributes)
319        {
320                final ResourceResponse resourceResponse = new ResourceResponse();
321
322                final IResourceStream resourceStream = getResourceStream();
323
324                // bail out if resource stream could not be found
325                if (resourceStream == null)
326                {
327                        return sendResourceError(resourceResponse, HttpServletResponse.SC_NOT_FOUND,
328                                "Unable to find resource");
329                }
330
331                // add Last-Modified header (to support HEAD requests and If-Modified-Since)
332                final Instant lastModified = resourceStream.lastModifiedTime();
333
334                resourceResponse.setLastModified(lastModified);
335
336                if (resourceResponse.dataNeedsToBeWritten(attributes))
337                {
338                        String contentType = resourceStream.getContentType();
339
340                        if (contentType == null && Application.exists())
341                        {
342                                contentType = Application.get().getMimeType(path);
343                        }
344
345                        // set Content-Type (may be null)
346                        resourceResponse.setContentType(contentType);
347
348                        // set content encoding (may be null)
349                        resourceResponse.setTextEncoding(getTextEncoding());
350
351                        // supports accept range
352                        resourceResponse.setAcceptRange(ContentRangeType.BYTES);
353
354                        try
355                        {
356                                // read resource data to get the content length
357                                InputStream inputStream = resourceStream.getInputStream();
358
359                                byte[] bytes = null;
360                                // send Content-Length header
361                                if (readBuffered)
362                                {
363                                        bytes = IOUtils.toByteArray(inputStream);
364                                        resourceResponse.setContentLength(bytes.length);
365                                }
366                                else
367                                {
368                                        resourceResponse.setContentLength(resourceStream.length().bytes());
369                                }
370
371                                // get content range information
372                                RequestCycle cycle = RequestCycle.get();
373                                Long startbyte = cycle.getMetaData(CONTENT_RANGE_STARTBYTE);
374                                Long endbyte = cycle.getMetaData(CONTENT_RANGE_ENDBYTE);
375
376                                // send response body with resource data
377                                PartWriterCallback partWriterCallback = new PartWriterCallback(bytes != null
378                                        ? new ByteArrayInputStream(bytes) : inputStream,
379                                        resourceResponse.getContentLength(), startbyte, endbyte);
380
381                                // If read buffered is set to false ensure the part writer callback is going to
382                                // close the input stream
383                                resourceResponse.setWriteCallback(partWriterCallback.setClose(!readBuffered));
384                        }
385                        catch (IOException e)
386                        {
387                                log.debug(e.getMessage(), e);
388                                return sendResourceError(resourceResponse, 500, "Unable to read resource stream");
389                        }
390                        catch (ResourceStreamNotFoundException e)
391                        {
392                                log.debug(e.getMessage(), e);
393                                return sendResourceError(resourceResponse, 500, "Unable to open resource stream");
394                        }
395                        finally
396                        {
397                                try
398                                {
399                                        if (readBuffered)
400                                        {
401                                                IOUtils.close(resourceStream);
402                                        }
403                                }
404                                catch (IOException e)
405                                {
406                                        log.warn("Unable to close the resource stream", e);
407                                }
408                        }
409                }
410
411                return resourceResponse;
412        }
413
414        /**
415         * Gives a chance to modify the resource going to be written in the response
416         * 
417         * @param attributes
418         *            current request attributes from client
419         * @param original
420         *            the original response
421         * @return the processed response
422         */
423        protected byte[] processResponse(final Attributes attributes, final byte[] original)
424        {
425                return compressResponse(attributes, original);
426        }
427
428        /**
429         * Compresses the response if its is eligible and there is a configured compressor
430         *
431         * @param attributes
432         *       *            current request attributes from client
433         *       * @param original
434         *       *            the original response
435         *       * @return the compressed response
436         */
437        protected byte[] compressResponse(final Attributes attributes, final byte[] original)
438        {
439                ITextResourceCompressor compressor = getCompressor();
440
441                if (compressor != null && getCompress())
442                {
443                        try
444                        {
445                                Charset charset = getProcessingEncoding();
446                                String nonCompressed = new String(original, charset);
447                                String output;
448                                if (compressor instanceof IScopeAwareTextResourceProcessor)
449                                {
450                                        IScopeAwareTextResourceProcessor scopeAwareProcessor = (IScopeAwareTextResourceProcessor)compressor;
451                                        output = scopeAwareProcessor.process(nonCompressed, getScope(), name);
452                                }
453                                else
454                                {
455                                        output = compressor.compress(nonCompressed);
456                                }
457                                final String textEncoding = getTextEncoding();
458                                final Charset outputCharset;
459                                if (Strings.isEmpty(textEncoding))
460                                {
461                                        outputCharset = charset;
462                                }
463                                else
464                                {
465                                        outputCharset = Charset.forName(textEncoding);
466                                }
467                                return output.getBytes(outputCharset);
468                        }
469                        catch (Exception e)
470                        {
471                                log.error("Error while compressing the content", e);
472                                return original;
473                        }
474                }
475                else
476                {
477                        // don't strip the comments
478                        return original;
479                }
480        }
481
482        /**
483         * @return The charset to use to read the resource
484         */
485        protected Charset getProcessingEncoding()
486        {
487                return StandardCharsets.UTF_8;
488        }
489
490        /**
491         * Gets the {@link IJavaScriptCompressor} to be used. By default returns the configured
492         * compressor on application level, but can be overriden by the user application to provide
493         * compressor specific to the resource.
494         *
495         * @return the configured application level JavaScript compressor. May be {@code null}.
496         */
497        protected ITextResourceCompressor getCompressor()
498        {
499                return null;
500        }
501
502        /**
503         * send resource specific error message and write log entry
504         * 
505         * @param resourceResponse
506         *            resource response
507         * @param errorCode
508         *            error code (=http status)
509         * @param errorMessage
510         *            error message (=http error message)
511         * @return resource response for method chaining
512         */
513        private ResourceResponse sendResourceError(ResourceResponse resourceResponse, int errorCode,
514                String errorMessage)
515        {
516                String msg = String.format(
517                        "resource [path = %s, style = %s, variation = %s, locale = %s]: %s (status=%d)",
518                        absolutePath, style, variation, locale, errorMessage, errorCode);
519
520                log.warn(msg);
521
522                resourceResponse.setError(errorCode, errorMessage);
523                return resourceResponse;
524        }
525
526        /**
527         * locate resource stream for current resource
528         * 
529         * @return resource stream or <code>null</code> if not found
530         */
531        @Override
532        public IResourceStream getResourceStream()
533        {
534                return internalGetResourceStream(getCurrentStyle(), getCurrentLocale());
535        }
536
537        /**
538         * @return whether {@link org.apache.wicket.resource.ITextResourceCompressor} can be used to
539         *         compress the resource.
540         */
541        public boolean getCompress()
542        {
543                return compress;
544        }
545
546        /**
547         * @param compress
548         *            A flag indicating whether the resource should be compressed.
549         */
550        public void setCompress(boolean compress)
551        {
552                this.compress = compress;
553        }
554
555        private IResourceStream internalGetResourceStream(final String style, final Locale locale)
556        {
557                IResourceStreamLocator resourceStreamLocator = Application.get()
558                        .getResourceSettings()
559                        .getResourceStreamLocator();
560                IResourceStream resourceStream = resourceStreamLocator.locate(getScope(), absolutePath,
561                        style, variation, locale, null, false);
562
563                String realPath = absolutePath;
564                if (resourceStream instanceof IFixedLocationResourceStream)
565                {
566                        realPath = ((IFixedLocationResourceStream)resourceStream).locationAsString();
567                        if (realPath != null)
568                        {
569                                int index = realPath.indexOf(absolutePath);
570                                if (index != -1)
571                                {
572                                        realPath = realPath.substring(index);
573                                }
574                        }
575                        else
576                        {
577                                realPath = absolutePath;
578                        }
579
580                }
581
582                if (accept(realPath) == false)
583                {
584                        throw new PackageResourceBlockedException(
585                                "Access denied to (static) package resource " + absolutePath +
586                                        ". See IPackageResourceGuard");
587                }
588
589                if (resourceStream != null)
590                {
591                        resourceStream = new ProcessingResourceStream(resourceStream);
592                }
593                return resourceStream;
594        }
595
596        /**
597         * An IResourceStream that processes the input stream of the original IResourceStream
598         */
599        private class ProcessingResourceStream extends ResourceStreamWrapper
600        {
601                private static final long serialVersionUID = 1L;
602
603                private ProcessingResourceStream(IResourceStream delegate)
604                {
605                        super(delegate);
606                }
607
608                @Override
609                public InputStream getInputStream() throws ResourceStreamNotFoundException
610                {
611                        byte[] bytes = null;
612                        InputStream inputStream = super.getInputStream();
613
614                        if (readBuffered)
615                        {
616                                try
617                                {
618                                        bytes = IOUtils.toByteArray(inputStream);
619                                }
620                                catch (IOException iox)
621                                {
622                                        throw new WicketRuntimeException(iox);
623                                }
624                                finally
625                                {
626                                        IOUtils.closeQuietly(this);
627                                }
628                        }
629
630                        RequestCycle cycle = RequestCycle.get();
631                        Attributes attributes;
632                        if (cycle != null)
633                        {
634                                attributes = new Attributes(cycle.getRequest(), cycle.getResponse());
635                        }
636                        else
637                        {
638                                // use empty request and response in case of non-http thread. WICKET-5532
639                                attributes = new Attributes(new MockWebRequest(Url.parse("")), new StringResponse());
640                        }
641                        if (bytes != null)
642                        {
643                                byte[] processedBytes = processResponse(attributes, bytes);
644                                return new ByteArrayInputStream(processedBytes);
645                        }
646                        else
647                        {
648                                return inputStream;
649                        }
650                }
651        }
652
653        /**
654         * Checks whether access is granted for this resource.
655         *
656         * By default IPackageResourceGuard is used to check the permissions but the resource itself can
657         * also make the check.
658         *
659         * @param path
660         *            resource path
661         * @return <code>true<code> if resource access is granted
662         */
663        protected boolean accept(String path)
664        {
665                IPackageResourceGuard guard = Application.get()
666                        .getResourceSettings()
667                        .getPackageResourceGuard();
668
669                return guard.accept(path);
670        }
671
672        /**
673         * Checks whether a resource for a given set of criteria exists.
674         *
675         * @param key
676         *            The key that contains all attributes about the requested resource
677         * @return {@code true} if there is a package resource with the given attributes
678         */
679        public static boolean exists(final ResourceReference.Key key)
680        {
681                return exists(key.getScopeClass(), key.getName(), key.getLocale(), key.getStyle(),
682                        key.getVariation());
683        }
684
685        /**
686         * Checks whether a resource for a given set of criteria exists.
687         * 
688         * @param scope
689         *            This argument will be used to get the class loader for loading the package
690         *            resource, and to determine what package it is in. Typically this is the class in
691         *            which you call this method
692         * @param path
693         *            The path to the resource
694         * @param locale
695         *            The locale of the resource
696         * @param style
697         *            The style of the resource (see {@link org.apache.wicket.Session})
698         * @param variation
699         *            The component's variation (of the style)
700         * @return {@code true} if a resource could be loaded, {@code false} otherwise
701         */
702        public static boolean exists(final Class<?> scope, final String path, final Locale locale,
703                final String style, final String variation)
704        {
705                String absolutePath = Packages.absolutePath(scope, path);
706                return Application.get()
707                        .getResourceSettings()
708                        .getResourceStreamLocator()
709                        .locate(scope, absolutePath, style, variation, locale, null, false) != null;
710        }
711
712        @Override
713        public String toString()
714        {
715                final StringBuilder result = new StringBuilder();
716                result.append('[')
717                        .append(Classes.simpleName(getClass()))
718                        .append(' ')
719                        .append("name = ")
720                        .append(path)
721                        .append(", scope = ")
722                        .append(scopeName)
723                        .append(", locale = ")
724                        .append(locale)
725                        .append(", style = ")
726                        .append(style)
727                        .append(", variation = ")
728                        .append(variation)
729                        .append(']');
730                return result.toString();
731        }
732
733        @Override
734        public int hashCode()
735        {
736                final int prime = 31;
737                int result = 1;
738                result = prime * result + ((absolutePath == null) ? 0 : absolutePath.hashCode());
739                result = prime * result + ((locale == null) ? 0 : locale.hashCode());
740                result = prime * result + ((path == null) ? 0 : path.hashCode());
741                result = prime * result + ((scopeName == null) ? 0 : scopeName.hashCode());
742                result = prime * result + ((style == null) ? 0 : style.hashCode());
743                result = prime * result + ((variation == null) ? 0 : variation.hashCode());
744                return result;
745        }
746
747        @Override
748        public boolean equals(Object obj)
749        {
750                if (this == obj)
751                        return true;
752                if (obj == null)
753                        return false;
754                if (getClass() != obj.getClass())
755                        return false;
756
757                PackageResource other = (PackageResource)obj;
758
759                return Objects.equals(absolutePath, other.absolutePath) &&
760                        Objects.equals(locale, other.locale) && Objects.equals(path, other.path) &&
761                        Objects.equals(scopeName, other.scopeName) && Objects.equals(style, other.style) &&
762                        Objects.equals(variation, other.variation);
763        }
764
765        String getParentFolderPlaceholder()
766        {
767                String parentFolderPlaceholder;
768                if (Application.exists())
769                {
770                        parentFolderPlaceholder = Application.get()
771                                .getResourceSettings()
772                                .getParentFolderPlaceholder();
773                }
774                else
775                {
776                        parentFolderPlaceholder = "..";
777                }
778                return parentFolderPlaceholder;
779        }
780
781        private static class CacheKey implements Serializable
782        {
783                private final String scopeName;
784                private final String path;
785                private final Locale locale;
786                private final String style;
787                private final String variation;
788
789                public CacheKey(String scopeName, String path, Locale locale, String style, String variation)
790                {
791                        this.scopeName = scopeName;
792                        this.path = path;
793                        this.locale = locale;
794                        this.style = style;
795                        this.variation = variation;
796                }
797
798                @Override
799                public boolean equals(Object o)
800                {
801                        if (this == o)
802                                return true;
803                        if (!(o instanceof CacheKey))
804                                return false;
805
806                        CacheKey cacheKey = (CacheKey)o;
807
808                        return Objects.equals(locale, cacheKey.locale) && Objects.equals(path, cacheKey.path) &&
809                                Objects.equals(scopeName, cacheKey.scopeName) &&
810                                Objects.equals(style, cacheKey.style) &&
811                                Objects.equals(variation, cacheKey.variation);
812                }
813
814                @Override
815                public int hashCode()
816                {
817                        int result = scopeName.hashCode();
818                        result = 31 * result + path.hashCode();
819                        result = 31 * result + (locale != null ? locale.hashCode() : 0);
820                        result = 31 * result + (style != null ? style.hashCode() : 0);
821                        result = 31 * result + (variation != null ? variation.hashCode() : 0);
822                        return result;
823                }
824
825                @Override
826                public String toString()
827                {
828                        final StringBuilder sb = new StringBuilder();
829                        sb.append("CacheKey");
830                        sb.append("{scopeName='").append(scopeName).append('\'');
831                        sb.append(", path='").append(path).append('\'');
832                        sb.append(", locale=").append(locale);
833                        sb.append(", style='").append(style).append('\'');
834                        sb.append(", variation='").append(variation).append('\'');
835                        sb.append('}');
836                        return sb.toString();
837                }
838        }
839
840        /**
841         * If the package resource should be read buffered.<br>
842         * <br>
843         * WARNING - if the stream is not read buffered compressors will not work, because they require
844         * the whole content to be read into memory.<br>
845         * ({@link org.apache.wicket.javascript.IJavaScriptCompressor}, <br>
846         * {@link org.apache.wicket.css.ICssCompressor}, <br>
847         * {@link org.apache.wicket.resource.IScopeAwareTextResourceProcessor})
848         * 
849         * @param readBuffered
850         *            if the package resource should be read buffered
851         * @return the current package resource
852         */
853        public PackageResource readBuffered(boolean readBuffered)
854        {
855                this.readBuffered = readBuffered;
856                return this;
857        }
858}