001    /*
002     * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved.
003     *
004     * Project and contact information: http://www.cascading.org/
005     *
006     * This file is part of the Cascading project.
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     *     http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing, software
015     * distributed under the License is distributed on an "AS IS" BASIS,
016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017     * See the License for the specific language governing permissions and
018     * limitations under the License.
019     */
020    
021    package cascading.tap;
022    
023    import java.io.IOException;
024    import java.io.Serializable;
025    import java.util.Set;
026    
027    import cascading.flow.Flow;
028    import cascading.flow.FlowElement;
029    import cascading.flow.FlowException;
030    import cascading.flow.FlowProcess;
031    import cascading.flow.planner.Scope;
032    import cascading.pipe.Pipe;
033    import cascading.property.ConfigDef;
034    import cascading.scheme.Scheme;
035    import cascading.tuple.Fields;
036    import cascading.tuple.FieldsResolverException;
037    import cascading.tuple.Tuple;
038    import cascading.tuple.TupleEntryCollector;
039    import cascading.tuple.TupleEntryIterator;
040    import cascading.util.Util;
041    
042    /**
043     * A Tap represents the physical data source or sink in a connected {@link cascading.flow.Flow}.
044     * </p>
045     * That is, a source Tap is the head end of a connected {@link Pipe} and {@link Tuple} stream, and
046     * a sink Tap is the tail end. Kinds of Tap types are used to manage files from a local disk,
047     * distributed disk, remote storage like Amazon S3, or via FTP. It simply abstracts
048     * out the complexity of connecting to these types of data sources.
049     * <p/>
050     * A Tap takes a {@link Scheme} instance, which is used to identify the type of resource (text file, binary file, etc).
051     * A Tap is responsible for how the resource is reached.
052     * <p/>
053     * By default when planning a Flow, Tap equality is a function of the {@link #getIdentifier()} and {@link #getScheme()}
054     * values. That is, two Tap instances are the same Tap instance if they sink/source the same resource and sink/source
055     * the same fields.
056     * <p/>
057     * Some more advanced taps, like a database tap, may need to extend equality to include any filtering, like the
058     * {@code where} clause in a SQL statement so two taps reading from the same SQL table aren't considered equal.
059     * <p/>
060     * Taps are also used to determine dependencies between two or more {@link Flow} instances when used with a
061     * {@link cascading.cascade.Cascade}. In that case the {@link #getFullIdentifier(Object)} value is used and the Scheme
062     * is ignored.
063     */
064    public abstract class Tap<Config, Input, Output> implements FlowElement, Serializable
065      {
066      /** Field scheme */
067      private Scheme<Config, Input, Output, ?, ?> scheme;
068    
069      /** Field mode */
070      SinkMode sinkMode = SinkMode.KEEP;
071    
072      private ConfigDef configDef;
073    
074      private ConfigDef processConfigDef;
075    
076      /** Field id */
077      private String id = null;
078      /** Field trace */
079      private final String trace = Util.captureDebugTrace( getClass() );
080    
081      /**
082       * Convenience function to make an array of Tap instances.
083       *
084       * @param taps of type Tap
085       * @return Tap array
086       */
087      public static Tap[] taps( Tap... taps )
088        {
089        return taps;
090        }
091    
092      /**
093       * Creates and returns a unique ID for the given Tap, this value is cached and may be used to uniquely identify
094       * the Tap instance in properties files etc.
095       * <p/>
096       * This value is generally reproducible assuming the Tap identifier and the Scheme source and sink Fields remain consistent.
097       *
098       * @param tap of type Tap
099       * @return of type String
100       */
101      public static synchronized String id( Tap tap )
102        {
103        if( tap.id == null )
104          tap.id = Util.createUniqueID();
105    
106        return tap.id;
107        }
108    
109      protected Tap()
110        {
111        }
112    
113      protected Tap( Scheme<Config, Input, Output, ?, ?> scheme )
114        {
115        this.setScheme( scheme );
116        }
117    
118      protected Tap( Scheme<Config, Input, Output, ?, ?> scheme, SinkMode sinkMode )
119        {
120        this.setScheme( scheme );
121        this.sinkMode = sinkMode;
122        }
123    
124      protected void setScheme( Scheme<Config, Input, Output, ?, ?> scheme )
125        {
126        this.scheme = scheme;
127        }
128    
129      /**
130       * Method getScheme returns the scheme of this Tap object.
131       *
132       * @return the scheme (type Scheme) of this Tap object.
133       */
134      public Scheme<Config, Input, Output, ?, ?> getScheme()
135        {
136        return scheme;
137        }
138    
139      /**
140       * Method getTrace return the trace of this object.
141       *
142       * @return String
143       */
144      public String getTrace()
145        {
146        return trace;
147        }
148    
149      /**
150       * Method flowInit allows this Tap instance to initialize itself in context of the given {@link cascading.flow.Flow} instance.
151       * This method is guaranteed to be called before the Flow is started and the
152       * {@link cascading.flow.FlowListener#onStarting(cascading.flow.Flow)} event is fired.
153       * <p/>
154       * This method will be called once per Flow, and before {@link #sourceConfInit(cascading.flow.FlowProcess, Object)} and
155       * {@link #sinkConfInit(cascading.flow.FlowProcess, Object)} methods.
156       *
157       * @param flow of type Flow
158       */
159      public void flowConfInit( Flow<Config> flow )
160        {
161    
162        }
163    
164      /**
165       * Method sourceConfInit initializes this instance as a source.
166       * <p/>
167       * This method maybe called more than once if this Tap instance is used outside the scope of a {@link cascading.flow.Flow}
168       * instance or if it participates in multiple times in a given Flow or across different Flows in
169       * a {@link cascading.cascade.Cascade}.
170       * <p/>
171       * In the context of a Flow, it will be called after
172       * {@link cascading.flow.FlowListener#onStarting(cascading.flow.Flow)}
173       * <p/>
174       * Note that no resources or services should be modified by this method.
175       *
176       * @param flowProcess of type FlowProcess
177       * @param conf        of type Config
178       */
179      public void sourceConfInit( FlowProcess<Config> flowProcess, Config conf )
180        {
181        getScheme().sourceConfInit( flowProcess, this, conf );
182        }
183    
184      /**
185       * Method sinkConfInit initializes this instance as a sink.
186       * <p/>
187       * This method maybe called more than once if this Tap instance is used outside the scope of a {@link cascading.flow.Flow}
188       * instance or if it participates in multiple times in a given Flow or across different Flows in
189       * a {@link cascading.cascade.Cascade}.
190       * <p/>
191       * Note this method will be called in context of this Tap being used as a traditional 'sink' and as a 'trap'.
192       * <p/>
193       * In the context of a Flow, it will be called after
194       * {@link cascading.flow.FlowListener#onStarting(cascading.flow.Flow)}
195       * <p/>
196       * Note that no resources or services should be modified by this method. If this Tap instance returns true for
197       * {@link #isReplace()}, then {@link #deleteResource(Object)} will be called by the parent Flow.
198       *
199       * @param flowProcess of type FlowProcess
200       * @param conf        of type Config
201       */
202      public void sinkConfInit( FlowProcess<Config> flowProcess, Config conf )
203        {
204        getScheme().sinkConfInit( flowProcess, this, conf );
205        }
206    
207      /**
208       * Method getIdentifier returns a String representing the resource this Tap instance represents.
209       * <p/>
210       * Often, if the tap accesses a filesystem, the identifier is nothing more than the path to the file or directory.
211       * In other cases it may be a an URL or URI representing a connection string or remote resource.
212       * <p/>
213       * Any two Tap instances having the same value for the identifier are considered equal.
214       *
215       * @return String
216       */
217      public abstract String getIdentifier();
218    
219      /**
220       * Method getSourceFields returns the sourceFields of this Tap object.
221       *
222       * @return the sourceFields (type Fields) of this Tap object.
223       */
224      public Fields getSourceFields()
225        {
226        return getScheme().getSourceFields();
227        }
228    
229      /**
230       * Method getSinkFields returns the sinkFields of this Tap object.
231       *
232       * @return the sinkFields (type Fields) of this Tap object.
233       */
234      public Fields getSinkFields()
235        {
236        return getScheme().getSinkFields();
237        }
238    
239      /**
240       * Method openForRead opens the resource represented by this Tap instance for reading.
241       * <p/>
242       * {@code input} value may be null, if so, sub-classes must inquire with the underlying {@link Scheme}
243       * via {@link Scheme#sourceConfInit(cascading.flow.FlowProcess, Tap, Object)} to get the proper
244       * input type and instantiate it before calling {@code super.openForRead()}.
245       * <p/>
246       * Note the returned iterator will return the same instance of {@link cascading.tuple.TupleEntry} on every call,
247       * thus a copy must be made of either the TupleEntry or the underlying {@code Tuple} instance if they are to be
248       * stored in a Collection.
249       *
250       * @param flowProcess of type FlowProcess
251       * @param input       of type Input
252       * @return TupleEntryIterator
253       * @throws java.io.IOException when the resource cannot be opened
254       */
255      public abstract TupleEntryIterator openForRead( FlowProcess<Config> flowProcess, Input input ) throws IOException;
256    
257      /**
258       * Method openForRead opens the resource represented by this Tap instance for reading.
259       * <p/>
260       * Note the returned iterator will return the same instance of {@link cascading.tuple.TupleEntry} on every call,
261       * thus a copy must be made of either the TupleEntry or the underlying {@code Tuple} instance if they are to be
262       * stored in a Collection.
263       *
264       * @param flowProcess of type FlowProcess
265       * @return TupleEntryIterator
266       * @throws java.io.IOException when the resource cannot be opened
267       */
268      public TupleEntryIterator openForRead( FlowProcess<Config> flowProcess ) throws IOException
269        {
270        return openForRead( flowProcess, null );
271        }
272    
273      /**
274       * Method openForWrite opens the resource represented by this Tap instance for writing.
275       * <p/>
276       * This method is used internally and does not honor the {@link SinkMode} setting. If SinkMode is
277       * {@link SinkMode#REPLACE}, this call may fail. See {@link #openForWrite(cascading.flow.FlowProcess)}.
278       * <p/>
279       * {@code output} value may be null, if so, sub-classes must inquire with the underlying {@link Scheme}
280       * via {@link Scheme#sinkConfInit(cascading.flow.FlowProcess, Tap, Object)} to get the proper
281       * output type and instantiate it before calling {@code super.openForWrite()}.
282       *
283       * @param flowProcess of type FlowProcess
284       * @param output      of type Output
285       * @return TupleEntryCollector
286       * @throws java.io.IOException when the resource cannot be opened
287       */
288      public abstract TupleEntryCollector openForWrite( FlowProcess<Config> flowProcess, Output output ) throws IOException;
289    
290      /**
291       * Method openForWrite opens the resource represented by this Tap instance for writing.
292       * <p/>
293       * This method is for user application use and does honor the {@link SinkMode#REPLACE} settings. That is, if
294       * SinkMode is set to {@link SinkMode#REPLACE} the underlying resource will be deleted.
295       * <p/>
296       * Note if {@link SinkMode#UPDATE} is set, the resource will not be deleted.
297       *
298       * @param flowProcess of type FlowProcess
299       * @return TupleEntryCollector
300       * @throws java.io.IOException when the resource cannot be opened
301       */
302      public TupleEntryCollector openForWrite( FlowProcess<Config> flowProcess ) throws IOException
303        {
304        if( isReplace() )
305          deleteResource( flowProcess.getConfigCopy() );
306    
307        return openForWrite( flowProcess, null );
308        }
309    
310      @Override
311      public Scope outgoingScopeFor( Set<Scope> incomingScopes )
312        {
313        // as a source Tap, we emit the scheme defined Fields
314        // as a sink Tap, we declare we emit the incoming Fields
315        // as a temp Tap, this method never gets called, but we emit what we consume
316        int count = 0;
317        for( Scope incomingScope : incomingScopes )
318          {
319          Fields incomingFields = incomingScope.getIncomingTapFields();
320    
321          if( incomingFields != null )
322            {
323            try
324              {
325              incomingFields.select( getSinkFields() );
326              }
327            catch( FieldsResolverException exception )
328              {
329              throw new TapException( this, exception.getSourceFields(), exception.getSelectorFields(), exception );
330              }
331    
332            count++;
333            }
334          }
335    
336        if( count > 1 )
337          throw new FlowException( "Tap may not have more than one incoming Scope" );
338    
339        // this allows the incoming to be passed through to the outgoing
340        Fields incomingFields = incomingScopes.size() == 0 ? null : incomingScopes.iterator().next().getIncomingTapFields();
341    
342        if( incomingFields != null &&
343          ( isSource() && getSourceFields().equals( Fields.UNKNOWN ) ||
344            isSink() && getSinkFields().equals( Fields.ALL ) ) )
345          return new Scope( incomingFields );
346    
347        if( count == 1 )
348          return new Scope( getSinkFields() );
349    
350        return new Scope( getSourceFields() );
351        }
352    
353      /**
354       * A hook for allowing a Scheme to lazily retrieve its source fields.
355       *
356       * @param flowProcess of type FlowProcess
357       * @return the found Fields
358       */
359      public Fields retrieveSourceFields( FlowProcess<Config> flowProcess )
360        {
361        return getScheme().retrieveSourceFields( flowProcess, this );
362        }
363    
364      public void presentSourceFields( FlowProcess<Config> flowProcess, Fields fields )
365        {
366        getScheme().presentSourceFields( flowProcess, this, fields );
367        }
368    
369      /**
370       * A hook for allowing a Scheme to lazily retrieve its sink fields.
371       *
372       * @param flowProcess of type FlowProcess
373       * @return the found Fields
374       */
375      public Fields retrieveSinkFields( FlowProcess<Config> flowProcess )
376        {
377        return getScheme().retrieveSinkFields( flowProcess, this );
378        }
379    
380      public void presentSinkFields( FlowProcess<Config> flowProcess, Fields fields )
381        {
382        getScheme().presentSinkFields( flowProcess, this, fields );
383        }
384    
385      @Override
386      public Fields resolveIncomingOperationArgumentFields( Scope incomingScope )
387        {
388        return incomingScope.getIncomingTapFields();
389        }
390    
391      @Override
392      public Fields resolveIncomingOperationPassThroughFields( Scope incomingScope )
393        {
394        return incomingScope.getIncomingTapFields();
395        }
396    
397      /**
398       * Method getFullIdentifier returns a fully qualified resource identifier.
399       *
400       * @param flowProcess of type FlowProcess
401       * @return String
402       */
403      public String getFullIdentifier( FlowProcess<Config> flowProcess )
404        {
405        return getFullIdentifier( flowProcess.getConfigCopy() );
406        }
407    
408      /**
409       * Method getFullIdentifier returns a fully qualified resource identifier.
410       *
411       * @param conf of type Config
412       * @return String
413       */
414      public String getFullIdentifier( Config conf )
415        {
416        return getIdentifier();
417        }
418    
419      /**
420       * Method createResource creates the underlying resource.
421       *
422       * @param flowProcess of type FlowProcess
423       * @return boolean
424       * @throws IOException when there is an error making directories
425       */
426      public boolean createResource( FlowProcess<Config> flowProcess ) throws IOException
427        {
428        return createResource( flowProcess.getConfigCopy() );
429        }
430    
431      /**
432       * Method createResource creates the underlying resource.
433       *
434       * @param conf of type Config
435       * @return boolean
436       * @throws IOException when there is an error making directories
437       */
438      public abstract boolean createResource( Config conf ) throws IOException;
439    
440      /**
441       * Method deleteResource deletes the resource represented by this instance.
442       *
443       * @param flowProcess of type FlowProcess
444       * @return boolean
445       * @throws IOException when the resource cannot be deleted
446       */
447      public boolean deleteResource( FlowProcess<Config> flowProcess ) throws IOException
448        {
449        return deleteResource( flowProcess.getConfigCopy() );
450        }
451    
452      /**
453       * Method deleteResource deletes the resource represented by this instance.
454       *
455       * @param conf of type Config
456       * @return boolean
457       * @throws IOException when the resource cannot be deleted
458       */
459      public abstract boolean deleteResource( Config conf ) throws IOException;
460    
461      /**
462       * Method commitResource allows the underlying resource to be notified when all write processing is
463       * successful so that any additional cleanup or processing may be completed.
464       * <p/>
465       * See {@link #rollbackResource(Object)} to handle cleanup in the face of failures.
466       * <p/>
467       * This method is invoked once "client side" and not in the cluster, if any.
468       * <p/>
469       * If other sink Tap instance in a given Flow fail on commitResource after called on this instance,
470       * rollbackResource will not be called.
471       * <p/>
472       * <emphasis>This is an experimental API and subject to refinement!!</emphasis>
473       *
474       * @param conf of type Config
475       * @return returns true if successful
476       * @throws IOException
477       */
478      public boolean commitResource( Config conf ) throws IOException
479        {
480        return true;
481        }
482    
483      /**
484       * Method rollbackResource allows the underlying resource to be notified when any write processing has failed or
485       * was stopped so that any cleanup may be started.
486       * <p/>
487       * See {@link #commitResource(Object)} to handle cleanup when the write has successfully completed.
488       * <p/>
489       * This method is invoked once "client side" and not in the cluster, if any.
490       * <p/>
491       * <emphasis>This is an experimental API and subject to refinement!!</emphasis>
492       *
493       * @param conf of type Config
494       * @return returns true if successful
495       * @throws IOException
496       */
497      public boolean rollbackResource( Config conf ) throws IOException
498        {
499        return true;
500        }
501    
502      /**
503       * Method resourceExists returns true if the path represented by this instance exists.
504       *
505       * @param flowProcess of type FlowProcess
506       * @return true if the underlying resource already exists
507       * @throws IOException when the status cannot be determined
508       */
509      public boolean resourceExists( FlowProcess<Config> flowProcess ) throws IOException
510        {
511        return resourceExists( flowProcess.getConfigCopy() );
512        }
513    
514      /**
515       * Method resourceExists returns true if the path represented by this instance exists.
516       *
517       * @param conf of type Config
518       * @return true if the underlying resource already exists
519       * @throws IOException when the status cannot be determined
520       */
521      public abstract boolean resourceExists( Config conf ) throws IOException;
522    
523      /**
524       * Method getModifiedTime returns the date this resource was last modified.
525       *
526       * @param flowProcess of type FlowProcess
527       * @return The date this resource was last modified.
528       * @throws IOException
529       */
530      public long getModifiedTime( FlowProcess<Config> flowProcess ) throws IOException
531        {
532        return getModifiedTime( flowProcess.getConfigCopy() );
533        }
534    
535      /**
536       * Method getModifiedTime returns the date this resource was last modified.
537       *
538       * @param conf of type Config
539       * @return The date this resource was last modified.
540       * @throws IOException
541       */
542      public abstract long getModifiedTime( Config conf ) throws IOException;
543    
544      /**
545       * Method getSinkMode returns the {@link SinkMode} }of this Tap object.
546       *
547       * @return the sinkMode (type SinkMode) of this Tap object.
548       */
549      public SinkMode getSinkMode()
550        {
551        return sinkMode;
552        }
553    
554      /**
555       * Method isKeep indicates whether the resource represented by this instance should be kept if it
556       * already exists when the Flow is started.
557       *
558       * @return boolean
559       */
560      public boolean isKeep()
561        {
562        return sinkMode == SinkMode.KEEP;
563        }
564    
565      /**
566       * Method isReplace indicates whether the resource represented by this instance should be deleted if it
567       * already exists when the Flow is started.
568       *
569       * @return boolean
570       */
571      public boolean isReplace()
572        {
573        return sinkMode == SinkMode.REPLACE;
574        }
575    
576      /**
577       * Method isUpdate indicates whether the resource represented by this instance should be updated if it already
578       * exists. Otherwise a new resource will be created, via {@link #createResource(Object)}, when the Flow is started.
579       *
580       * @return boolean
581       */
582      public boolean isUpdate()
583        {
584        return sinkMode == SinkMode.UPDATE;
585        }
586    
587      /**
588       * Method isSink returns true if this Tap instance can be used as a sink.
589       *
590       * @return boolean
591       */
592      public boolean isSink()
593        {
594        return getScheme().isSink();
595        }
596    
597      /**
598       * Method isSource returns true if this Tap instance can be used as a source.
599       *
600       * @return boolean
601       */
602      public boolean isSource()
603        {
604        return getScheme().isSource();
605        }
606    
607      /**
608       * Method isTemporary returns true if this Tap is temporary (used for intermediate results).
609       *
610       * @return the temporary (type boolean) of this Tap object.
611       */
612      public boolean isTemporary()
613        {
614        return false;
615        }
616    
617      /**
618       * Returns a {@link cascading.property.ConfigDef} instance that allows for local properties to be set and made available via
619       * a resulting {@link cascading.flow.FlowProcess} instance when the tap is invoked.
620       * <p/>
621       * Any properties set on the configDef will not show up in any {@link Flow} or {@link cascading.flow.FlowStep} process
622       * level configuration, but will override any of those values as seen by the current Tap instance method call where a
623       * FlowProcess is provided except for the {@link #sourceConfInit(cascading.flow.FlowProcess, Object)} and
624       * {@link #sinkConfInit(cascading.flow.FlowProcess, Object)} methods.
625       * <p/>
626       * That is, the {@code *confInit} methods are called before any ConfigDef is applied, so any values placed into
627       * a ConfigDef instance will not be visible to them.
628       *
629       * @return an instance of ConfigDef
630       */
631      public ConfigDef getConfigDef()
632        {
633        if( configDef == null )
634          configDef = new ConfigDef();
635    
636        return configDef;
637        }
638    
639      /**
640       * Returns {@code true} if there are properties in the configDef instance.
641       *
642       * @return true if there are configDef properties
643       */
644      public boolean hasConfigDef()
645        {
646        return configDef != null && !configDef.isEmpty();
647        }
648    
649      /**
650       * Returns a {@link ConfigDef} instance that allows for process level properties to be set and made available via
651       * a resulting {@link cascading.flow.FlowProcess} instance when the tap is invoked.
652       * <p/>
653       * Any properties set on the stepConfigDef will not show up in any Flow configuration, but will show up in
654       * the current process {@link cascading.flow.FlowStep} (in Hadoop the MapReduce jobconf). Any value set in the
655       * stepConfigDef will be overridden by the tap local {@code #getConfigDef} instance.
656       * </p>
657       * Use this method to tweak properties in the process step this tap instance is planned into.
658       * <p/>
659       * Note the {@code *confInit} methods are called before any ConfigDef is applied, so any values placed into
660       * a ConfigDef instance will not be visible to them.
661       *
662       * @return an instance of ConfigDef
663       */
664      @Override
665      public ConfigDef getStepConfigDef()
666        {
667        if( processConfigDef == null )
668          processConfigDef = new ConfigDef();
669    
670        return processConfigDef;
671        }
672    
673      /**
674       * Returns {@code true} if there are properties in the processConfigDef instance.
675       *
676       * @return true if there are processConfigDef properties
677       */
678      @Override
679      public boolean hasStepConfigDef()
680        {
681        return processConfigDef != null && !processConfigDef.isEmpty();
682        }
683    
684      @Override
685      public boolean isEquivalentTo( FlowElement element )
686        {
687        if( element == null )
688          return false;
689    
690        if( this == element )
691          return true;
692    
693        boolean compare = getClass() == element.getClass();
694    
695        if( !compare )
696          return false;
697    
698        return equals( element );
699        }
700    
701      @Override
702      public boolean equals( Object object )
703        {
704        if( this == object )
705          return true;
706        if( object == null || getClass() != object.getClass() )
707          return false;
708    
709        Tap tap = (Tap) object;
710    
711        if( getIdentifier() != null ? !getIdentifier().equals( tap.getIdentifier() ) : tap.getIdentifier() != null )
712          return false;
713    
714        if( getScheme() != null ? !getScheme().equals( tap.getScheme() ) : tap.getScheme() != null )
715          return false;
716    
717        return true;
718        }
719    
720      @Override
721      public int hashCode()
722        {
723        int result = getIdentifier() != null ? getIdentifier().hashCode() : 0;
724    
725        result = 31 * result + ( getScheme() != null ? getScheme().hashCode() : 0 );
726    
727        return result;
728        }
729    
730      @Override
731      public String toString()
732        {
733        if( getIdentifier() != null )
734          return getClass().getSimpleName() + "[\"" + getScheme() + "\"]" + "[\"" + Util.sanitizeUrl( getIdentifier() ) + "\"]"; // sanitize
735        else
736          return getClass().getSimpleName() + "[\"" + getScheme() + "\"]" + "[not initialized]";
737        }
738      }