View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2009 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.impl.wsdl.panels.teststeps.support;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Component;
17  import java.awt.Point;
18  import java.awt.datatransfer.DataFlavor;
19  import java.awt.datatransfer.Transferable;
20  import java.awt.dnd.DnDConstants;
21  import java.awt.dnd.DropTarget;
22  import java.awt.dnd.DropTargetDragEvent;
23  import java.awt.dnd.DropTargetDropEvent;
24  import java.awt.dnd.DropTargetEvent;
25  import java.awt.dnd.DropTargetListener;
26  import java.awt.event.ActionEvent;
27  import java.io.BufferedReader;
28  import java.io.File;
29  import java.io.FileReader;
30  import java.io.IOException;
31  import java.util.Arrays;
32  import java.util.HashSet;
33  import java.util.Set;
34  
35  import javax.swing.AbstractAction;
36  import javax.swing.Action;
37  import javax.swing.JButton;
38  import javax.swing.JPanel;
39  import javax.swing.JScrollPane;
40  import javax.swing.JTable;
41  import javax.swing.ListSelectionModel;
42  import javax.swing.SwingUtilities;
43  import javax.swing.TransferHandler;
44  import javax.swing.event.ListSelectionEvent;
45  import javax.swing.event.ListSelectionListener;
46  import javax.swing.table.AbstractTableModel;
47  
48  import com.eviware.soapui.SoapUI;
49  import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
50  import com.eviware.soapui.model.TestPropertyHolder;
51  import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
52  import com.eviware.soapui.model.propertyexpansion.PropertyExpansionImpl;
53  import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
54  import com.eviware.soapui.model.support.TestPropertyUtils;
55  import com.eviware.soapui.model.testsuite.TestProperty;
56  import com.eviware.soapui.model.testsuite.TestPropertyListener;
57  import com.eviware.soapui.model.tree.nodes.PropertyTreeNode.PropertyModelItem;
58  import com.eviware.soapui.support.StringUtils;
59  import com.eviware.soapui.support.UISupport;
60  import com.eviware.soapui.support.components.JXToolBar;
61  import com.eviware.soapui.support.types.StringList;
62  import com.eviware.soapui.support.xml.XmlUtils;
63  import com.eviware.x.form.XFormDialog;
64  import com.eviware.x.form.support.ADialogBuilder;
65  import com.eviware.x.form.support.AField;
66  import com.eviware.x.form.support.AForm;
67  import com.eviware.x.form.support.AField.AFieldType;
68  
69  public class PropertyHolderTable extends JPanel
70  {
71  	private final TestPropertyHolder holder;
72  	private PropertiesModel propertiesModel;
73  	private RemovePropertyAction removePropertyAction;
74  	private AddPropertyAction addPropertyAction;
75  	private InternalTestPropertyListener testPropertyListener;
76  	private JTable propertiesTable;
77  	private JXToolBar toolbar;
78  	private LoadPropertiesAction loadPropertiesAction;
79  	private MovePropertyUpAction movePropertyUpAction;
80  	private MovePropertyDownAction movePropertyDownAction;
81  
82  	public PropertyHolderTable( TestPropertyHolder holder )
83  	{
84  		super( new BorderLayout() );
85  		this.holder = holder;
86  
87  		loadPropertiesAction = new LoadPropertiesAction();
88  		testPropertyListener = new InternalTestPropertyListener();
89  		holder.addTestPropertyListener( testPropertyListener );
90  
91  		JScrollPane scrollPane = new JScrollPane( buildPropertiesTable() );
92  
93  		if( getHolder().getModelItem() != null )
94  		{
95  			DropTarget dropTarget = new DropTarget( scrollPane, new PropertyHolderTablePropertyExpansionDropTarget() );
96  			dropTarget.setDefaultActions( DnDConstants.ACTION_COPY_OR_MOVE );
97  		}
98  
99  		add( scrollPane, BorderLayout.CENTER );
100 		add( buildToolbar(), BorderLayout.NORTH );
101 	}
102 
103 	protected JTable buildPropertiesTable()
104 	{
105 		propertiesModel = new PropertiesModel();
106 		propertiesTable = new PropertiesHolderJTable();
107 		propertiesTable.setSurrendersFocusOnKeystroke( true );
108 
109 		propertiesTable.putClientProperty( "terminateEditOnFocusLost", Boolean.TRUE );
110 		propertiesTable.getSelectionModel().addListSelectionListener( new ListSelectionListener()
111 		{
112 			public void valueChanged( ListSelectionEvent e )
113 			{
114 				int selectedRow = propertiesTable.getSelectedRow();
115 				if( removePropertyAction != null )
116 					removePropertyAction.setEnabled( selectedRow != -1 );
117 
118 				if( movePropertyUpAction != null )
119 					movePropertyUpAction.setEnabled( selectedRow > 0 );
120 
121 				if( movePropertyDownAction != null )
122 					movePropertyDownAction.setEnabled( selectedRow >= 0 && selectedRow < propertiesTable.getRowCount() - 1 );
123 			}
124 		} );
125 
126 		propertiesTable.setDragEnabled( true );
127 		propertiesTable.setTransferHandler( new TransferHandler( "testProperty" ) );
128 
129 		if( getHolder().getModelItem() != null )
130 		{
131 			DropTarget dropTarget = new DropTarget( propertiesTable, new PropertyHolderTablePropertyExpansionDropTarget() );
132 			dropTarget.setDefaultActions( DnDConstants.ACTION_COPY_OR_MOVE );
133 		}
134 
135 		return propertiesTable;
136 	}
137 
138 	public class PropertiesHolderJTable extends JTable
139 	{
140 		public PropertiesHolderJTable()
141 		{
142 			super( propertiesModel );
143 			setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
144 			// setAutoStartEditOnKeyStroke( true );
145 			setSurrendersFocusOnKeystroke( true );
146 			setRowHeight( 19 );
147 			// setHorizontalScrollEnabled(true);
148 		}
149 
150 		public PropertyModelItem getTestProperty()
151 		{
152 			int index = getSelectedRow();
153 			if( index == -1 )
154 				return null;
155 			TestProperty property = propertiesModel.getPropertyAtRow( index );
156 			return new PropertyModelItem( property, true );
157 		}
158 	}
159 
160 	private Component buildToolbar()
161 	{
162 		toolbar = UISupport.createSmallToolbar();
163 
164 		if( holder instanceof MutableTestPropertyHolder )
165 		{
166 			removePropertyAction = new RemovePropertyAction();
167 			addPropertyAction = new AddPropertyAction();
168 			movePropertyUpAction = new MovePropertyUpAction();
169 			movePropertyDownAction = new MovePropertyDownAction();
170 
171 			JButton addPropertyButton = UISupport.createToolbarButton( addPropertyAction );
172 			toolbar.add( addPropertyButton );
173 			JButton removePropertyButton = UISupport.createToolbarButton( removePropertyAction );
174 			toolbar.add( removePropertyButton );
175 
176 			JButton movePropertyUpButton = UISupport.createToolbarButton( movePropertyUpAction );
177 			toolbar.add( movePropertyUpButton );
178 			JButton movePropertyDownButton = UISupport.createToolbarButton( movePropertyDownAction );
179 			toolbar.add( movePropertyDownButton );
180 		}
181 
182 		JButton clearPropertiesButton = UISupport.createToolbarButton( new ClearPropertiesAction() );
183 		toolbar.add( clearPropertiesButton );
184 		JButton loadPropertiesButton = UISupport.createToolbarButton( loadPropertiesAction );
185 		toolbar.add( loadPropertiesButton );
186 		toolbar.add( UISupport.createToolbarButton( new SavePropertiesAction() ) );
187 
188 		return toolbar;
189 	}
190 
191 	public JXToolBar getToolbar()
192 	{
193 		return toolbar;
194 	}
195 
196 	public JTable getPropertiesTable()
197 	{
198 		return propertiesTable;
199 	}
200 
201 	public void release()
202 	{
203 		if( propertiesTable.isEditing() )
204 			propertiesTable.getCellEditor().stopCellEditing();
205 
206 		holder.removeTestPropertyListener( testPropertyListener );
207 	}
208 
209 	public void setEnabled( boolean enabled )
210 	{
211 		addPropertyAction.setEnabled( enabled );
212 		removePropertyAction.setEnabled( enabled );
213 		propertiesTable.setEnabled( enabled );
214 		loadPropertiesAction.setEnabled( enabled );
215 
216 		super.setEnabled( enabled );
217 	}
218 
219 	private final class InternalTestPropertyListener implements TestPropertyListener
220 	{
221 		private boolean enabled = true;
222 
223 		public boolean isEnabled()
224 		{
225 			return enabled;
226 		}
227 
228 		public void setEnabled( boolean enabled )
229 		{
230 			this.enabled = enabled;
231 		}
232 
233 		public void propertyAdded( String name )
234 		{
235 			if( enabled )
236 				propertiesModel.fireTableDataChanged();
237 		}
238 
239 		public void propertyRemoved( String name )
240 		{
241 			if( enabled )
242 				propertiesModel.fireTableDataChanged();
243 		}
244 
245 		public void propertyRenamed( String oldName, String newName )
246 		{
247 			if( enabled )
248 				propertiesModel.fireTableDataChanged();
249 		}
250 
251 		public void propertyValueChanged( String name, String oldValue, String newValue )
252 		{
253 			if( enabled )
254 				propertiesModel.fireTableDataChanged();
255 		}
256 
257 		public void propertyMoved( String name, int oldIndex, int newIndex )
258 		{
259 			if( enabled )
260 				propertiesModel.fireTableDataChanged();
261 		}
262 	}
263 
264 	private class PropertiesModel extends AbstractTableModel
265 	{
266 		private StringList names = new StringList();
267 
268 		public PropertiesModel()
269 		{
270 			names = new StringList( holder.getPropertyNames() );
271 		}
272 
273 		public int getRowCount()
274 		{
275 			return names.size();
276 		}
277 
278 		public int getColumnCount()
279 		{
280 			return 2;
281 		}
282 
283 		@Override
284 		public void fireTableDataChanged()
285 		{
286 			names = new StringList( holder.getPropertyNames() );
287 			super.fireTableDataChanged();
288 		}
289 
290 		public String getColumnName( int columnIndex )
291 		{
292 			switch( columnIndex )
293 			{
294 			case 0 :
295 				return "Name";
296 			case 1 :
297 				return "Value";
298 			}
299 
300 			return null;
301 		}
302 
303 		public boolean isCellEditable( int rowIndex, int columnIndex )
304 		{
305 			if( columnIndex == 0 )
306 			{
307 				return holder instanceof MutableTestPropertyHolder;
308 			}
309 
310 			return !holder.getProperty( names.get( rowIndex ) ).isReadOnly();
311 		}
312 
313 		public void setValueAt( Object aValue, int rowIndex, int columnIndex )
314 		{
315 			TestProperty property = holder.getProperty( names.get( rowIndex ) );
316 			switch( columnIndex )
317 			{
318 			case 0 :
319 			{
320 				if( holder instanceof MutableTestPropertyHolder )
321 				{
322 					TestProperty prop = holder.getProperty( aValue.toString() );
323 					if( prop != null && prop != property )
324 					{
325 						UISupport.showErrorMessage( "Property name exists!" );
326 						return;
327 					}
328 					( ( MutableTestPropertyHolder )holder ).renameProperty( property.getName(), aValue.toString() );
329 				}
330 				break;
331 			}
332 			case 1 :
333 			{
334 				property.setValue( aValue.toString() );
335 				break;
336 			}
337 			}
338 		}
339 
340 		@Override
341 		public Class<?> getColumnClass( int columnIndex )
342 		{
343 			return String.class;
344 		}
345 
346 		public TestProperty getPropertyAtRow( int rowIndex )
347 		{
348 			return holder.getProperty( names.get( rowIndex ) );
349 		}
350 
351 		public Object getValueAt( int rowIndex, int columnIndex )
352 		{
353 			TestProperty property = holder.getProperty( names.get( rowIndex ) );
354 			if( property == null )
355 				return null;
356 
357 			switch( columnIndex )
358 			{
359 			case 0 :
360 				return property.getName();
361 			case 1 :
362 				return property.getValue();
363 			}
364 
365 			return null;
366 		}
367 	}
368 
369 	private class AddPropertyAction extends AbstractAction
370 	{
371 		public AddPropertyAction()
372 		{
373 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/add_property.gif" ) );
374 			putValue( Action.SHORT_DESCRIPTION, "Adds a property to the property list" );
375 		}
376 
377 		public void actionPerformed( ActionEvent e )
378 		{
379 			String name = UISupport.prompt( "Specify unique property name", "Add Property", "" );
380 			if( StringUtils.hasContent( name ) )
381 			{
382 				if( holder.hasProperty( name ) )
383 				{
384 					UISupport.showErrorMessage( "Property name [" + name + "] already exists.." );
385 					return;
386 				}
387 
388 				( ( MutableTestPropertyHolder )holder ).addProperty( name );
389 				final int row = holder.getPropertyNames().length - 1;
390 				propertiesModel.fireTableRowsInserted( row, row );
391 				SwingUtilities.invokeLater( new Runnable()
392 				{
393 					public void run()
394 					{
395 						requestFocusInWindow();
396 						scrollRectToVisible( propertiesTable.getCellRect( row, 1, true ) );
397 						SwingUtilities.invokeLater( new Runnable()
398 						{
399 							public void run()
400 							{
401 								propertiesTable.editCellAt( row, 1 );
402 								Component editorComponent = propertiesTable.getEditorComponent();
403 								if( editorComponent != null )
404 									editorComponent.requestFocusInWindow();
405 							}
406 						} );
407 					}
408 				} );
409 
410 			}
411 		}
412 	}
413 
414 	private class RemovePropertyAction extends AbstractAction
415 	{
416 		public RemovePropertyAction()
417 		{
418 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/remove_property.gif" ) );
419 			putValue( Action.SHORT_DESCRIPTION, "Removes the selected property from the property list" );
420 			setEnabled( false );
421 		}
422 
423 		public void actionPerformed( ActionEvent e )
424 		{
425 			int row = propertiesTable.getSelectedRow();
426 			if( row == -1 )
427 				return;
428 
429 			UISupport.stopCellEditing( propertiesTable );
430 
431 			String propertyName = propertiesModel.getValueAt( row, 0 ).toString();
432 			if( UISupport.confirm( "Remove property [" + propertyName + "]?", "Remove Property" ) )
433 			{
434 				( ( MutableTestPropertyHolder )holder ).removeProperty( propertyName );
435 				propertiesModel.fireTableRowsDeleted( row, row );
436 			}
437 		}
438 	}
439 
440 	private class ClearPropertiesAction extends AbstractAction
441 	{
442 		public ClearPropertiesAction()
443 		{
444 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/clear_properties.gif" ) );
445 			putValue( Action.SHORT_DESCRIPTION, "Clears all current property values" );
446 		}
447 
448 		public void actionPerformed( ActionEvent e )
449 		{
450 			if( UISupport.confirm( "Clear all property values?", "Clear Properties" ) )
451 			{
452 				for( String name : holder.getPropertyNames() )
453 				{
454 					holder.getProperty( name ).setValue( null );
455 				}
456 			}
457 		}
458 	}
459 
460 	private class MovePropertyUpAction extends AbstractAction
461 	{
462 		public MovePropertyUpAction()
463 		{
464 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/up_arrow.gif" ) );
465 			putValue( Action.SHORT_DESCRIPTION, "Moves selected property up one row" );
466 			setEnabled( false );
467 		}
468 
469 		public void actionPerformed( ActionEvent e )
470 		{
471 			int ix = propertiesTable.getSelectedRow();
472 			if( ix != -1 )
473 			{
474 				( ( MutableTestPropertyHolder )holder ).moveProperty( holder.getPropertyAt( ix ).getName(), ix - 1 );
475 				propertiesTable.setRowSelectionInterval( ix - 1, ix - 1 );
476 			}
477 		}
478 	}
479 
480 	private class MovePropertyDownAction extends AbstractAction
481 	{
482 		public MovePropertyDownAction()
483 		{
484 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/down_arrow.gif" ) );
485 			putValue( Action.SHORT_DESCRIPTION, "Moves selected property down one row" );
486 			setEnabled( false );
487 		}
488 
489 		public void actionPerformed( ActionEvent e )
490 		{
491 			int ix = propertiesTable.getSelectedRow();
492 			if( ix != -1 )
493 			{
494 				( ( MutableTestPropertyHolder )holder ).moveProperty( holder.getPropertyAt( ix ).getName(), ix + 1 );
495 
496 				propertiesTable.setRowSelectionInterval( ix + 1, ix + 1 );
497 			}
498 		}
499 	}
500 
501 	private class LoadPropertiesAction extends AbstractAction
502 	{
503 		private XFormDialog dialog;
504 
505 		public LoadPropertiesAction()
506 		{
507 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/load_properties.gif" ) );
508 			putValue( Action.SHORT_DESCRIPTION, "Loads property values from an external file" );
509 		}
510 
511 		public void actionPerformed( ActionEvent e )
512 		{
513 			if( dialog == null )
514 				dialog = ADialogBuilder.buildDialog( LoadOptionsForm.class );
515 
516 			dialog.getFormField( LoadOptionsForm.DELETEREMAINING )
517 					.setEnabled( holder instanceof MutableTestPropertyHolder );
518 			dialog.getFormField( LoadOptionsForm.CREATEMISSING ).setEnabled( holder instanceof MutableTestPropertyHolder );
519 
520 			if( dialog.show() )
521 			{
522 				try
523 				{
524 					BufferedReader reader = new BufferedReader( new FileReader( dialog.getValue( LoadOptionsForm.FILE ) ) );
525 
526 					String line = reader.readLine();
527 					int count = 0;
528 
529 					Set<String> names = new HashSet<String>( Arrays.asList( holder.getPropertyNames() ) );
530 
531 					while( line != null )
532 					{
533 						if( line.trim().length() > 0 && !( line.charAt( 0 ) == '#' ) )
534 						{
535 							int ix = line.indexOf( '=' );
536 							if( ix > 0 )
537 							{
538 								String name = line.substring( 0, ix ).trim();
539 								String value = line.length() > ix ? line.substring( ix + 1 ) : "";
540 
541 								// read multiline value
542 								if( value.endsWith( "//" ) )
543 								{
544 									value = value.substring( 0, value.length() - 1 );
545 
546 									String ln = reader.readLine();
547 									while( ln != null && ln.endsWith( "//" ) )
548 									{
549 										value += ln.substring( 0, ln.length() - 1 );
550 									}
551 
552 									if( ln != null )
553 										value += ln;
554 									if( ln == null )
555 										break;
556 								}
557 
558 								if( holder.hasProperty( name ) )
559 								{
560 									count++ ;
561 									holder.setPropertyValue( name, value );
562 								}
563 								else if( dialog.getBooleanValue( LoadOptionsForm.CREATEMISSING )
564 										&& holder instanceof MutableTestPropertyHolder )
565 								{
566 									( ( MutableTestPropertyHolder )holder ).addProperty( name ).setValue( value );
567 									count++ ;
568 								}
569 
570 								names.remove( name );
571 							}
572 						}
573 
574 						line = reader.readLine();
575 					}
576 
577 					if( dialog.getBooleanValue( LoadOptionsForm.DELETEREMAINING )
578 							&& holder instanceof MutableTestPropertyHolder )
579 					{
580 						for( String name : names )
581 						{
582 							( ( MutableTestPropertyHolder )holder ).removeProperty( name );
583 						}
584 					}
585 
586 					reader.close();
587 					UISupport.showInfoMessage( "Added/Updated " + count + " properties from file" );
588 				}
589 				catch( Exception ex )
590 				{
591 					UISupport.showErrorMessage( ex );
592 				}
593 			}
594 		}
595 	}
596 
597 	private class SavePropertiesAction extends AbstractAction
598 	{
599 		public SavePropertiesAction()
600 		{
601 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/set_properties_target.gif" ) );
602 			putValue( Action.SHORT_DESCRIPTION, "Saves current property-values to a file" );
603 		}
604 
605 		public void actionPerformed( ActionEvent e )
606 		{
607 			if( holder.getPropertyCount() == 0 )
608 			{
609 				UISupport.showErrorMessage( "No properties to save!" );
610 				return;
611 			}
612 
613 			File file = UISupport.getFileDialogs().saveAs( this, "Save Properties" );
614 			if( file != null )
615 			{
616 				try
617 				{
618 					TestPropertyUtils.saveTo( holder, file.getAbsolutePath() );
619 				}
620 				catch( IOException e1 )
621 				{
622 					UISupport.showErrorMessage( e1 );
623 				}
624 			}
625 		}
626 	}
627 
628 	@AForm( name = "Load Properties", description = "Set load options below" )
629 	private static interface LoadOptionsForm
630 	{
631 		@AField( name = "File", description = "The Properties file to load", type = AFieldType.FILE )
632 		public static final String FILE = "File";
633 
634 		@AField( name = "Create Missing", description = "Creates Missing Properties", type = AFieldType.BOOLEAN )
635 		public static final String CREATEMISSING = "Create Missing";
636 
637 		@AField( name = "Delete Remaining", description = "Deletes properties not in file", type = AFieldType.BOOLEAN )
638 		public static final String DELETEREMAINING = "Delete Remaining";
639 	}
640 
641 	public TestPropertyHolder getHolder()
642 	{
643 		return holder;
644 	}
645 
646 	public PropertiesModel getPropertiesModel()
647 	{
648 		return propertiesModel;
649 	}
650 
651 	public final class PropertyHolderTablePropertyExpansionDropTarget implements DropTargetListener
652 	{
653 		public PropertyHolderTablePropertyExpansionDropTarget()
654 		{
655 		}
656 
657 		public void dragEnter( DropTargetDragEvent dtde )
658 		{
659 			if( !isAcceptable( dtde.getTransferable(), dtde.getLocation() ) )
660 				dtde.rejectDrag();
661 		}
662 
663 		public void dragExit( DropTargetEvent dtde )
664 		{
665 		}
666 
667 		public void dragOver( DropTargetDragEvent dtde )
668 		{
669 			if( !isAcceptable( dtde.getTransferable(), dtde.getLocation() ) )
670 			{
671 				dtde.rejectDrag();
672 			}
673 			else
674 			{
675 				dtde.acceptDrag( dtde.getDropAction() );
676 			}
677 		}
678 
679 		public void drop( DropTargetDropEvent dtde )
680 		{
681 			if( !isAcceptable( dtde.getTransferable(), dtde.getLocation() ) )
682 			{
683 				dtde.rejectDrop();
684 			}
685 			else
686 			{
687 				try
688 				{
689 					Transferable transferable = dtde.getTransferable();
690 					Object transferData = transferable.getTransferData( transferable.getTransferDataFlavors()[0] );
691 					if( transferData instanceof PropertyModelItem )
692 					{
693 						dtde.acceptDrop( dtde.getDropAction() );
694 						PropertyModelItem modelItem = ( PropertyModelItem )transferData;
695 
696 						String xpath = modelItem.getXPath();
697 						if( xpath == null && XmlUtils.seemsToBeXml( modelItem.getProperty().getValue() ) )
698 						{
699 							xpath = UISupport.selectXPath( "Create PropertyExpansion", "Select XPath below", modelItem
700 									.getProperty().getValue(), null );
701 
702 							if( xpath != null )
703 								xpath = XmlUtils.removeXPathNamespaceDeclarations( xpath );
704 						}
705 
706 						PropertyExpansion propertyExpansion = new PropertyExpansionImpl( modelItem.getProperty(), xpath );
707 
708 						Point point = dtde.getLocation();
709 						int column = getPropertiesTable().columnAtPoint( point );
710 						int row = getPropertiesTable().rowAtPoint( point );
711 
712 						if( row == -1 )
713 						{
714 							if( holder instanceof MutableTestPropertyHolder )
715 							{
716 								MutableTestPropertyHolder mtph = ( MutableTestPropertyHolder )holder;
717 								String name = UISupport.prompt( "Specify unique name of property", "Add Property", modelItem
718 										.getProperty().getName() );
719 								while( name != null && mtph.hasProperty( name ) )
720 								{
721 									name = UISupport.prompt( "Specify unique name of property", "Add Property", modelItem
722 											.getProperty().getName() );
723 								}
724 
725 								if( name != null )
726 									mtph.addProperty( name ).setValue( propertyExpansion.toString() );
727 							}
728 						}
729 						else
730 						{
731 							getPropertiesTable().setValueAt( propertyExpansion.toString(), row, column );
732 						}
733 
734 						dtde.dropComplete( true );
735 					}
736 				}
737 				catch( Exception e )
738 				{
739 					SoapUI.logError( e );
740 				}
741 			}
742 		}
743 
744 		public void dropActionChanged( DropTargetDragEvent dtde )
745 		{
746 		}
747 
748 		public boolean isAcceptable( Transferable transferable, Point point )
749 		{
750 			int row = getPropertiesTable().rowAtPoint( point );
751 			if( row >= 0 )
752 			{
753 				int column = getPropertiesTable().columnAtPoint( point );
754 				if( column != 1 )
755 					return false;
756 
757 				if( !getPropertiesTable().isCellEditable( row, column ) )
758 					return false;
759 			}
760 			else if( !( getHolder() instanceof MutableTestPropertyHolder ) )
761 			{
762 				return false;
763 			}
764 
765 			DataFlavor[] flavors = transferable.getTransferDataFlavors();
766 			for( int i = 0; i < flavors.length; i++ )
767 			{
768 				DataFlavor flavor = flavors[i];
769 				if( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
770 				{
771 					try
772 					{
773 						Object modelItem = transferable.getTransferData( flavor );
774 						if( modelItem instanceof PropertyModelItem
775 								&& ( ( PropertyModelItem )modelItem ).getProperty().getModelItem() != getHolder()
776 										.getModelItem() )
777 						{
778 							return PropertyExpansionUtils.canExpandProperty( getHolder().getModelItem(),
779 									( ( PropertyModelItem )modelItem ).getProperty() );
780 						}
781 					}
782 					catch( Exception ex )
783 					{
784 						SoapUI.logError( ex );
785 					}
786 				}
787 			}
788 
789 			return false;
790 		}
791 	}
792 }