Project

General

Profile

Download (21.4 KB) Statistics
| Branch: | Tag: | Revision:
1 666164d5 Cherian Mathew
package eu.etaxonomy.taxeditor.model;
2
3
import java.io.PrintWriter;
4
import java.io.StringWriter;
5 ee7d980d Cherian Mathew
import java.util.ArrayList;
6
import java.util.List;
7 666164d5 Cherian Mathew
8 e138e599 Cherian Mathew
import org.apache.commons.lang.exception.ExceptionUtils;
9 666164d5 Cherian Mathew
import org.apache.log4j.Logger;
10
import org.eclipse.core.runtime.IStatus;
11
import org.eclipse.core.runtime.MultiStatus;
12 fa518bf8 Cherian Mathew
import org.eclipse.core.runtime.Platform;
13 666164d5 Cherian Mathew
import org.eclipse.core.runtime.Status;
14
import org.eclipse.jface.dialogs.MessageDialog;
15
import org.eclipse.swt.widgets.Display;
16
17 5b6653af Patrick Plitzner
import eu.etaxonomy.cdm.common.CdmUtils;
18
import eu.etaxonomy.cdm.config.ICdmSource;
19 d0c15b1e Patrick Plitzner
import eu.etaxonomy.cdm.test.integration.SecurityExceptionUtils;
20 5b6653af Patrick Plitzner
import eu.etaxonomy.taxeditor.remoting.source.CdmRemoteSource;
21 fa518bf8 Cherian Mathew
import eu.etaxonomy.taxeditor.store.CdmStore;
22 666164d5 Cherian Mathew
import eu.etaxonomy.taxeditor.store.internal.TaxeditorStorePlugin;
23
24
/**
25
 * Utility class which handles all the messaging information generated by the
26
 * Editor.
27 5e581f5d Cherian Mathew
 *
28 666164d5 Cherian Mathew
 * This includes logging as well as dialogs.
29 5e581f5d Cherian Mathew
 *
30 666164d5 Cherian Mathew
 * @author cmathew
31
 *
32
 */
33
public class MessagingUtils {
34 ee7d980d Cherian Mathew
    public final static String UNEXPECTED_ERROR_MESSAGE = "This is an unexpected error.";
35
    public final static String CONTACT_MESSAGE = System.getProperty("line.separator") +  "Please contact EDIT Support (EditSupport@bgbm.org) with the error trace below (click on the 'Details' button).";
36 b8548a34 Cherian Mathew
    public final static String DEFAULT_MESSAGE = "Error thrown but no associated message";
37 65e61086 Cherian Mathew
    public final static String CONNECTION_FAILURE_MESSAGE = "The connection to the remote server has been broken";
38 5291be06 Katja Luther
    public final static String REMOTE_ACCESS_FAILURE_MESSAGE = "Maybe the server is currently not available. If the problem persists please contact the server admin with the error trace below.";
39 9d0bce28 Katja Luther
    public static final String WIDGET_IS_DISPOSED_MESSAGE = "A widget was called, which was already disposed";
40 ee7d980d Cherian Mathew
41
    /**
42
     * Gets the Log4J logger for a given class
43
     *
44
     * @param clazz
45
     *            a {@link java.lang.Class} object.
46
     * @return a {@link org.apache.log4j.Logger} object.
47
     */
48
    public static Logger getLog4JLogger(Class clazz) {
49
        return Logger.getLogger(clazz);
50
    }
51
52
    /**
53
     * Logs details from a given Status object
54
     *
55
     * @param status
56
     * 				a {@link org.eclipse.core.runtime.IStatus} object.
57
     */
58
    private static void log(IStatus status) {
59
        TaxeditorStorePlugin.getDefault().getLog().log(status);
60
    }
61
62
    /**
63
     * Logs a status object as information.
64
     *
65
     * @param status
66
     *            a {@link org.eclipse.core.runtime.IStatus} object.
67
     */
68
    public static void info(IStatus status) {
69
        log(status);
70
    }
71
72
    /**
73
     * Logs a string as information.
74
     *
75
     * @param message
76
     *            a {@link java.lang.String} object.
77
     */
78
    public static void info(String message) {
79
        IStatus status = new Status(IStatus.INFO, AbstractUtility.getPluginId(), message);
80
        info(status);
81
    }
82
83
    /**
84
     * Logs an exception from a given source as a warning.
85
     *
86
     * @param source
87
     * @param t
88
     */
89
    public static void warn(Class source, Throwable t) {
90
        IStatus status = new Status(IStatus.WARNING, AbstractUtility.getPluginId(), t.getMessage(), t);
91
        MessagingUtils.getLog4JLogger(source).warn(t);
92
        log(status);
93
    }
94
95
    /**
96
     * Logs a status object from a given source as a warning.
97
     *
98
     * @param source
99
     * @param status
100
     */
101
    public static void warn(Class source, IStatus status) {
102
        MessagingUtils.getLog4JLogger(source).warn(status.getMessage(), status.getException());
103
        log(status);
104
    }
105
106
    /**
107
     * Logs a string from a given source as a warning.
108
     *
109
     *
110
     * @param source
111
     *            a {@link java.lang.Class} object.
112
     * @param message
113
     *            a {@link java.lang.String} object.
114
     */
115
    public static void warn(Class source, String message) {
116
        IStatus status = new Status(IStatus.WARNING, AbstractUtility.getPluginId(), message);
117
        MessagingUtils.getLog4JLogger(source).warn(message);
118
        log(status);
119
    }
120
121
    /**
122
     * Logs a status object from a given source as an error.
123
     *
124
     *
125
     * @param source
126
     *            a {@link java.lang.Class} object.
127
     * @param status
128
     *            a {@link org.eclipse.core.runtime.IStatus} object.
129
     */
130
    public static void error(Class source, IStatus status) {
131
        getLog4JLogger(source)
132
        .error(status.getMessage(), status.getException());
133
        log(status);
134
    }
135
136
    /**
137
     * Logs a string and exception from a given source as an error.
138
     *
139
     *
140
     * @param source
141
     *            a {@link java.lang.Class} object.
142
     * @param message
143
     *            a {@link java.lang.String} object.
144
     * @param t
145
     *            a {@link java.lang.Throwable} object.
146
     */
147
    public static void error(Class source, String message, Throwable t) {
148
        IStatus status = new Status(IStatus.ERROR, AbstractUtility.getPluginId(), message, t);
149
        error(source, status);
150
    }
151
152
153
154
    /**
155
     * Logs an exception from a given source as an error.
156
     *
157
     *
158
     * @param source
159
     *            a {@link java.lang.Class} object.
160
     * @param t
161
     *            a {@link java.lang.Throwable} object.
162
     */
163
    public static void error(Class source, Throwable t) {
164
        error(source.getClass(), t.getMessage(), t);
165
    }
166
167 fa518bf8 Cherian Mathew
168
169 ee7d980d Cherian Mathew
    /**
170 fa518bf8 Cherian Mathew
     * Returns a list of strings, providing info on,
171
     *  - login
172
     *  - editor version
173
     *  - server (address + source name)
174
     *  - db schema version
175 ee7d980d Cherian Mathew
     *
176 fa518bf8 Cherian Mathew
     * @return
177 ee7d980d Cherian Mathew
     */
178 fa518bf8 Cherian Mathew
    public static List<String> getContextInfo() {
179
        List<String> contextInfo = new ArrayList<String>();
180
        String name = "";
181 5b6653af Patrick Plitzner
        String contextPath = "";
182 fa518bf8 Cherian Mathew
        String schemaVersion = "";
183
        String server = "";
184
        String version = "";
185
        String login = "";
186
        try {
187
            version = Platform.getBundle("eu.etaxonomy.taxeditor.application").getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
188
189 5b6653af Patrick Plitzner
            ICdmSource activeCdmSource = CdmStore.getActiveCdmSource();
190
            if(activeCdmSource != null ) {
191 fa518bf8 Cherian Mathew
                login = CdmStore.getLoginManager().getAuthenticatedUser().getUsername();
192 5b6653af Patrick Plitzner
                name = activeCdmSource.getName();
193
                schemaVersion = activeCdmSource.getDbSchemaVersion();
194
                server = activeCdmSource.getServer();
195
                if(activeCdmSource instanceof CdmRemoteSource){
196
                    contextPath = ((CdmRemoteSource) activeCdmSource).getContextPath();
197 a66eea38 Andreas Müller
                    if (contextPath != null && contextPath.startsWith("cdmserver/")){
198
                    	contextPath = contextPath.substring("cdmserver/".length());
199
                    }
200 5b6653af Patrick Plitzner
                }
201 fa518bf8 Cherian Mathew
            }
202 ee7d980d Cherian Mathew
203 fa518bf8 Cherian Mathew
        } catch (Exception e) {
204
            // Nothing to do
205
        }
206
        contextInfo.add("login : " + login);
207
        contextInfo.add("editor version : " + version);
208 a66eea38 Andreas Müller
        contextInfo.add("server : " + server + " (" + name + ")" + (CdmUtils.isNotBlank(contextPath)?" / "+contextPath:""));
209 fa518bf8 Cherian Mathew
        contextInfo.add("schema version : " + schemaVersion);
210 c5273839 Patrick Plitzner
        contextInfo.add("os : " + System.getProperty("os.name")+" "+System.getProperty("os.version")+" "+System.getProperty("os.arch"));
211
        contextInfo.add("java : "+System.getProperty("java.version"));
212 ee7d980d Cherian Mathew
213 fa518bf8 Cherian Mathew
        return contextInfo;
214
    }
215
216
    public static String getStackTraceAndContextInfo(Throwable t, List<String> contextInfo)  {
217
        StringBuffer stackTraceAndContextInfo = new StringBuffer();
218 b8548a34 Cherian Mathew
        Throwable throwable = t;
219 fa518bf8 Cherian Mathew
220
        for(String infoItem : contextInfo) {
221
            stackTraceAndContextInfo.append(infoItem + System.getProperty("line.separator"));
222
        }
223
224
        StringWriter sw = new StringWriter();
225 b8548a34 Cherian Mathew
226
        if(throwable == null) {
227
            throwable = getDefaultThrowable();
228
        }
229
        throwable.printStackTrace(new PrintWriter(sw));
230 fa518bf8 Cherian Mathew
231
        stackTraceAndContextInfo.append(sw.toString());
232
233
        return stackTraceAndContextInfo.toString();
234 ee7d980d Cherian Mathew
    }
235
236 369ac904 U-BGBM\k.luther
    public static String getContextInfo(List<String> contextInfo)  {
237
        StringBuffer scontextInfoStringBuffer = new StringBuffer();
238
239
240
        for(String infoItem : contextInfo) {
241
            scontextInfoStringBuffer.append(infoItem + System.getProperty("line.separator"));
242
        }
243
244
245
246
        return scontextInfoStringBuffer.toString();
247
    }
248
249 b8548a34 Cherian Mathew
    private static Throwable getDefaultThrowable() {
250
        return new Throwable("Error thrown but no associated exception");
251
    }
252
253
254
255 fa518bf8 Cherian Mathew
    /**
256
     * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
257
     *
258
     * @param title
259
     * @param source
260
     * @param t
261
     * @param contextInfo
262
     * @param message
263
     * @param status
264
     */
265 ee7d980d Cherian Mathew
    private static void errorDialog(final String title,
266
            final Object source,
267
            final Throwable t,
268 fa518bf8 Cherian Mathew
            final List<String> contextInfo,
269 ce2778dd Cherian Mathew
            final String message,
270 65e61086 Cherian Mathew
            final MultiStatus status,
271
            final boolean showReason) {
272 ee7d980d Cherian Mathew
273
        Display.getDefault().asyncExec(new Runnable() {
274
275
            @Override
276
            public void run() {
277 fa518bf8 Cherian Mathew
                String stackTraceWithContext = getStackTraceAndContextInfo(t, contextInfo);
278 65e61086 Cherian Mathew
                CdmErrorDialog ced = new CdmErrorDialog(AbstractUtility.getShell(), title, message, status, stackTraceWithContext, showReason);
279 ee7d980d Cherian Mathew
                ced.open();
280
                Class<? extends Object> clazz = source != null ? source.getClass() : this.getClass();
281
282 fa518bf8 Cherian Mathew
283 ee7d980d Cherian Mathew
                IStatus singleStatus = new Status(IStatus.ERROR,
284
                        status.getPlugin(),
285 ce2778dd Cherian Mathew
                        message,
286 fa518bf8 Cherian Mathew
                        new Exception(stackTraceWithContext));
287 ee7d980d Cherian Mathew
288
                error(clazz, singleStatus);
289
            }
290
        });
291
    }
292
293 65e61086 Cherian Mathew
    public static void errorDialog(final String title,
294
            final Object source,
295
            final String message,
296
            final String pluginId,
297
            final Throwable t,
298
            boolean addContactMesg) {
299
        errorDialog(title, source, message, pluginId, t, addContactMesg, true);
300
301
    }
302 ee7d980d Cherian Mathew
    /**
303
     * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
304
     *
305
     * @param title
306
     * @param source
307
     * @param message
308
     * @param pluginId
309
     * @param t
310
     */
311
    public static void errorDialog(final String title,
312
            final Object source,
313
            final String message,
314
            final String pluginId,
315
            final Throwable t,
316 65e61086 Cherian Mathew
            boolean addContactMesg,
317
            boolean showReason) {
318 ee7d980d Cherian Mathew
319 b8548a34 Cherian Mathew
        Throwable throwable = t;
320 ee7d980d Cherian Mathew
        StringBuffer sbStackTrace = new StringBuffer();
321
322
        // We need to build a MultiStatus object since the simple
323
        // idea of writing out the stack trace as a single string
324
        // leads to a single line on windows
325
        List<Status> childStatuses = new ArrayList<Status>();
326 fa518bf8 Cherian Mathew
327
        // add context info
328
        List<String> contextInfo = getContextInfo();
329
        for(String infoItem : contextInfo) {
330
            childStatuses.add(new Status(IStatus.ERROR, pluginId, infoItem));
331
        }
332
333 b8548a34 Cherian Mathew
        if(throwable == null) {
334
            throwable = getDefaultThrowable();
335
        }
336
337 e138e599 Cherian Mathew
        int thCount = 0;
338
        int maxTraces = 4;
339
340
        for(Throwable th : ExceptionUtils.getThrowables(throwable)) {
341
            // add main exception
342
            if(thCount == 0) {
343
                for (StackTraceElement ste : th.getStackTrace()) {
344
                    childStatuses.add(new Status(IStatus.ERROR, pluginId, "  at " + ste.toString()));
345
                }
346
            } else {
347
                // add recursive causes
348
                if(th != null) {
349
                    childStatuses.add(new Status(IStatus.ERROR, pluginId, ""));
350
                    String msg = th.toString();
351
                    childStatuses.add(new Status(IStatus.ERROR, pluginId, "Caused by : " + msg));
352
                    int traceCount = 0;
353
                    for (StackTraceElement ste : th.getStackTrace()) {
354
                        // add only pre-defined number of trace elements
355
                        if(traceCount > maxTraces) {
356
                            childStatuses.add(new Status(IStatus.ERROR, pluginId, "  ...."));
357
                            break;
358
                        }
359
                        // build & add status
360
                        childStatuses.add(new Status(IStatus.ERROR, pluginId, "  at " + ste.toString()));
361
                        traceCount++;
362
                    }
363
                }
364 ce2778dd Cherian Mathew
            }
365 e138e599 Cherian Mathew
            thCount++;
366 ce2778dd Cherian Mathew
        }
367 ee7d980d Cherian Mathew
        String finalMessage = message;
368
369
        if(finalMessage == null || finalMessage.isEmpty()) {
370 b8548a34 Cherian Mathew
            finalMessage = DEFAULT_MESSAGE;
371 ee7d980d Cherian Mathew
        }
372
373
        if(addContactMesg) {
374 fa518bf8 Cherian Mathew
            // add edit support contact info to message
375 ee7d980d Cherian Mathew
            finalMessage += MessagingUtils.CONTACT_MESSAGE;
376
        }
377
378
        MultiStatus ms = new MultiStatus(pluginId,
379
                IStatus.ERROR,
380
                childStatuses.toArray(new Status[] {}),
381 b8548a34 Cherian Mathew
                throwable.toString(),
382
                throwable);
383 ee7d980d Cherian Mathew
384 65e61086 Cherian Mathew
        errorDialog(title, source, throwable, contextInfo, finalMessage, ms, showReason);
385 fa518bf8 Cherian Mathew
    }
386 ee7d980d Cherian Mathew
387
    /**
388
     * Displays a dialog for an exception occurring in an operation.
389
     *
390
     * This will be either a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog} in case of a
391
     * security runtime exception or a warning {@link org.eclipse.jface.dialogs.MessageDialog} in
392
     * case of any other exception.
393
     *
394
     * @param title
395
     *            a {@link java.lang.String} object.
396
     * @param source
397
     *            a {@link java.lang.Object} object.
398
     * @param status
399
     *            a {@link org.eclipse.core.runtime.IStatus} object.
400
     */
401
    public static void operationDialog(final Object source,
402
            final Exception ex,
403
            final String pluginId,
404
            final String operationlabel,
405
            final String hint) {
406
407
        Display.getDefault().asyncExec(new Runnable() {
408
409
            @Override
410
            public void run() {
411
                MultiStatus info = null;
412
                String title = null;
413
414
                // FIXME cannot access TaxonomicEditorPlugin.PLUGIN_ID from here
415
                // String PID = TaxonomicEditorPlugin.PLUGIN_ID;
416
                String PID = "eu.etaxonomy.taxeditor.application";
417
418
                // checking security exceptions for every operation
419
                RuntimeException securityRuntimeException = SecurityExceptionUtils.findSecurityRuntimeException(ex);
420
421
                // in case of a security exception it is a warning, else it is an error
422
                if(securityRuntimeException != null){
423
                    title = "Your changes could not be saved!";
424
                    warningDialog(title, source, String.format("You are missing sufficient permissions for the operation \"%s\". %s", operationlabel, hint));
425
                } else {
426
                    title = "Error executing operation";
427
                    errorDialog(title, source, String.format("An error occured while executing %s. %s", operationlabel, hint), pluginId, ex, true);
428
429
                }
430
431
432
            }
433
        });
434
    }
435
436
437
438
439
    /**
440
     * Displays a question {@link org.eclipse.jface.dialogs.MessageDialog}.
441
     *
442
     * @param title
443
     *            a {@link java.lang.String} object.
444
     * @param message
445
     *            a {@link java.lang.String} object.
446
     * @return a boolean.
447
     */
448
    public static boolean confirmDialog(String title, String message) {
449
        return MessageDialog.openQuestion(AbstractUtility.getShell(), title, message);
450
    }
451
452
    /**
453
     * Displays a message {@link org.eclipse.jface.dialogs.MessageDialog}.
454
     *
455
     * @param title
456
     * @param source
457
     * @param message
458
     */
459
    public static void messageDialog(final String title, final Object source, final String message) {
460
        MessagingUtils.messageDialog(title, source, message, null, true);
461
    }
462
463
464
465
    /**
466
     * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
467
     *
468
     * @param title
469
     *            The dialogs title
470
     * @param source
471
     *            The object where the warning was generated (used by log4j)
472
     * @param message
473
     *            An informative String to be presented to the user
474
     * @param title
475
     *            The dialogs title
476
     * @param t
477
     *            a Throwable if one exists or null
478
     */
479
    public static void messageDialog(final String title,
480
            final Object source,
481
            final String message,
482
            final Throwable t) {
483
        MessagingUtils.messageDialog(title, source, message, t, true);
484
    }
485
486
    /**
487 5e581f5d Cherian Mathew
     * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
488
     *
489
     * @param title
490
     *            The dialogs title
491
     * @param source
492
     *            The object where the warning was generated (used by log4j)
493
     * @param message
494
     *            An informative String to be presented to the user
495
     * @param title
496
     *            The dialogs title
497
     * @param t
498
     *            a Throwable if one exists or null
499
     */
500 ee7d980d Cherian Mathew
    public static void messageDialog(final String title,
501
            final Object source,
502
            final String message,
503
            final Throwable t,
504
            boolean async) {
505
        if(async) {
506
            Display.getDefault().asyncExec(new Runnable() {
507
508
                @Override
509
                public void run() {
510
                    MessageDialog.openError(AbstractUtility.getShell(), title, message + getCauseRecursively(t));
511
                    Class<? extends Object> clazz = source != null ? source
512
                            .getClass() : this.getClass();
513
                            error(clazz, message, t);
514
                }
515
516
517
            });
518
        } else {
519
            MessageDialog.openError(AbstractUtility.getShell(), title, message + getCauseRecursively(t));
520
            Class<? extends Object> clazz = source != null ? source.getClass() : TaxeditorStorePlugin.class;
521
            error(clazz, message, t);
522
        }
523
    }
524 5e581f5d Cherian Mathew
525
    public static String getCauseRecursively(Throwable t) {
526
        if(t == null){
527
            return "";
528
        }
529
530
        if(t.getCause() != null){
531
            return getCauseRecursively(t.getCause());
532
        }else{
533
            return String.format("\n\nException: %s\nMessage: %s", t.getClass().getSimpleName(), t.getMessage());
534
        }
535
536
    }
537 ee7d980d Cherian Mathew
    /**
538
     * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
539
     *
540
     * @param title
541
     * @param termBase
542
     * @param status
543
     */
544
    public static void warningDialog(String title, Object source,
545
            IStatus status) {
546
        MessagingUtils.warningDialog(title, source, status.getMessage());
547
    }
548
549 bcad19b9 Cherian Mathew
    /**
550
     * Standard warning dialog for the case when the application is not yet connected to the datasource
551
     *
552
     * @param source
553
     */
554
    public static void noDataSourceWarningDialog(Object source) {
555
        MessagingUtils
556
        .warningDialog(
557
                "Application is not connected to a datastore",
558
                source,
559
                "The requested operation is only available when "
560
                + "connected to a datasource. You may choose a datasource to connect to or create a new one in the datasource view.");
561
    }
562
563 af45abb8 Katja Luther
    /**
564
     * Standard warning dialog for the case when the datasource is not available
565
     *
566
     * @param source
567
     */
568
    public static void dataSourceNotAvailableWarningDialog(Object source) {
569
        MessagingUtils
570
        .warningDialog(
571
                "The datasource is not available",
572
                source,
573 0bd095d9 Katja Luther
                "The editor is not connected to a datasource. Maybe the datasource is not available.");
574 af45abb8 Katja Luther
    }
575
576
577 ee7d980d Cherian Mathew
    /**
578
     * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
579
     *
580
     * @param title
581
     *            The dialogs title
582
     * @param source
583
     *            The object where the warning was generated (used by log4j)
584
     * @param message
585
     *            An informative String to be presented to the user
586
     */
587
    public static void warningDialog(final String title, final Object source, final String message) {
588
        Display.getDefault().asyncExec(new Runnable() {
589
590
            @Override
591
            public void run() {
592
                MessageDialog.openWarning(AbstractUtility.getShell(), title, message);
593
                Class<? extends Object> clazz = source != null ? source
594
                        .getClass() : AbstractUtility.class;
595
                        warn(clazz, message);
596
            }
597
        });
598
    }
599
600
    /**
601
     * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
602
     *
603
     * @param title
604
     * @param status
605
     */
606
    public static void informationDialog(final String title, final IStatus status) {
607
        MessagingUtils.informationDialog(title, status.getMessage());
608
    }
609
610
    /**
611
     * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
612
     *
613
     * @param title
614
     *            a {@link java.lang.String} object.
615
     * @param message
616
     *            a {@link java.lang.String} object.
617
     */
618
    public static void informationDialog(final String title,
619
            final String message) {
620
        Display.getDefault().asyncExec(new Runnable() {
621
622
            @Override
623
            public void run() {
624
                MessageDialog.openInformation(AbstractUtility.getShell(), title, message);
625
            }
626
        });
627
    }
628
629
    /**
630
     * Open a message box that informs the user about unimplemented
631
     * functionality. This method is for developer convenience.
632
     *
633
     * @param source
634
     *            a {@link java.lang.Object} object.
635
     */
636
    public static void notImplementedMessage(Object source) {
637
        warningDialog("Not yet implemented", source,
638
                "This functionality is not yet implemented.");
639
    }
640 666164d5 Cherian Mathew
641
}