View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 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.rest.panels.request.inspectors.schema;
14  
15  import java.awt.BorderLayout;
16  import java.awt.event.ActionEvent;
17  import java.awt.event.ActionListener;
18  import java.awt.event.ItemEvent;
19  import java.awt.event.ItemListener;
20  import java.beans.PropertyChangeEvent;
21  import java.beans.PropertyChangeListener;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.List;
26  
27  import javax.swing.AbstractAction;
28  import javax.swing.JButton;
29  import javax.swing.JCheckBox;
30  import javax.swing.JComponent;
31  import javax.swing.JList;
32  import javax.swing.JPanel;
33  import javax.swing.JScrollPane;
34  import javax.swing.JSplitPane;
35  import javax.swing.JTabbedPane;
36  import javax.swing.ListSelectionModel;
37  import javax.swing.event.ListSelectionEvent;
38  import javax.swing.event.ListSelectionListener;
39  import javax.xml.namespace.QName;
40  
41  import org.apache.xmlbeans.XmlException;
42  import org.apache.xmlbeans.XmlObject;
43  import org.apache.xmlbeans.XmlOptions;
44  
45  import com.eviware.soapui.impl.rest.RestRequest;
46  import com.eviware.soapui.impl.rest.RestService;
47  import com.eviware.soapui.impl.settings.XmlBeansSettingsImpl;
48  import com.eviware.soapui.impl.wadl.inference.ConflictHandler;
49  import com.eviware.soapui.impl.wsdl.submit.transports.http.HttpResponse;
50  import com.eviware.soapui.impl.wsdl.submit.transports.jms.JMSResponse;
51  import com.eviware.soapui.model.iface.Submit;
52  import com.eviware.soapui.model.iface.SubmitContext;
53  import com.eviware.soapui.model.iface.SubmitListener;
54  import com.eviware.soapui.model.iface.Submit.Status;
55  import com.eviware.soapui.support.UISupport;
56  import com.eviware.soapui.support.components.JXToolBar;
57  import com.eviware.soapui.support.editor.EditorView;
58  import com.eviware.soapui.support.editor.inspectors.AbstractXmlInspector;
59  import com.eviware.soapui.support.editor.views.xml.raw.RawXmlEditorFactory;
60  import com.eviware.soapui.support.editor.xml.XmlDocument;
61  import com.eviware.soapui.support.log.JLogList;
62  import com.eviware.soapui.support.xml.JXEditTextArea;
63  import com.eviware.soapui.support.xml.XmlUtils;
64  
65  /***
66   * @author Dain.Nilsson
67   */
68  public class InferredSchemaInspector extends AbstractXmlInspector implements SubmitListener
69  {
70  	private SchemaTabs tabs;
71  	private RestService service;
72  	private RestRequest request;
73  	private Handler handler;
74  	private Thread thread;
75  
76  	protected InferredSchemaInspector( RestRequest request )
77  	{
78  		super( "Schema", "Inferred Schema", true, InferredSchemaInspectorFactory.INSPECTOR_ID );
79  		service = request.getResource().getService();
80  		this.request = request;
81  
82  		request.addSubmitListener( this );
83  	}
84  
85  	public JComponent getComponent()
86  	{
87  		if( tabs == null )
88  		{
89  			tabs = new SchemaTabs();
90  			InferredSchemaManager.addPropertyChangeListener( service, tabs );
91  		}
92  
93  		return tabs;
94  	}
95  
96  	@Override
97  	public boolean isEnabledFor( EditorView<XmlDocument> view )
98  	{
99  		return !view.getViewId().equals( RawXmlEditorFactory.VIEW_ID );
100 	}
101 
102 	public void afterSubmit( Submit submit, SubmitContext context )
103 	{
104 		if( submit.getResponse() == null )
105 			return;
106 		HttpResponse httpResponse = ( HttpResponse )submit.getResponse();
107 		String content = httpResponse.getContentAsXml();
108 		if( content == null || content.equals( "<xml/>" ) )
109 			return;
110 		XmlObject xml;
111 		try
112 		{
113 			URL url = httpResponse.getURL();
114 			String defaultNamespace = null;
115 			if( url != null )
116 			{
117 				defaultNamespace = url.getProtocol() + "://" + url.getHost();
118 			}
119 			else
120 			{
121 				if( httpResponse instanceof JMSResponse )
122 				{
123 					defaultNamespace = ( ( JMSResponse )httpResponse ).getEndpoint();
124 				}
125 			}
126 			XmlOptions options = new XmlOptions().setLoadSubstituteNamespaces( Collections.singletonMap( "",
127 					defaultNamespace ) );
128 			xml = XmlObject.Factory.parse( content, options );
129 		}
130 		catch( XmlException e )
131 		{
132 			e.printStackTrace();
133 			return;
134 		}
135 		if( !submit.getStatus().equals( Status.CANCELED )
136 				&& !InferredSchemaManager.getInferredSchema( service ).validate( xml ) )
137 		{
138 			setTitle( "Schema (conflicts)" );
139 			if( thread != null && thread.isAlive() )
140 			{
141 				handler.kill();
142 				try
143 				{
144 					thread.join();
145 				}
146 				catch( InterruptedException e )
147 				{
148 					e.printStackTrace();
149 				}
150 			}
151 			handler = new Handler( tabs, xml );
152 			thread = new Thread( handler );
153 			thread.start();
154 		}
155 	}
156 
157 	public boolean beforeSubmit( Submit submit, SubmitContext context )
158 	{
159 		return true;
160 	}
161 
162 	public void release()
163 	{
164 		super.release();
165 
166 		request.removeSubmitListener( this );
167 		InferredSchemaManager.removePropertyChangeListener( service, tabs );
168 
169 		if( thread != null && thread.isAlive() )
170 		{
171 			handler.kill();
172 		}
173 	}
174 
175 	@SuppressWarnings( "serial" )
176 	private class SchemaTabs extends JTabbedPane implements ActionListener, PropertyChangeListener,
177 			ListSelectionListener
178 	{
179 		private JLogList log;
180 		private JPanel conflicts;
181 		private JButton resolveButton;
182 		private JCheckBox auto;
183 		private Handler handler;
184 		private JXEditTextArea xsd;
185 		private JList schemaList;
186 		public static final String AUTO_INFER_SCHEMAS = "AutoInferSchemas";
187 		public static final String NO_NAMESPACE = "<no namespace>";
188 
189 		public SchemaTabs()
190 		{
191 			super();
192 			conflicts = new JPanel();
193 			conflicts.setLayout( new BorderLayout() );
194 			auto = new JCheckBox( "Auto-Resolve" );
195 			auto.setToolTipText( "Automatically modify inferred schema from received Responses" );
196 			auto.setOpaque( false );
197 			UISupport.setFixedSize( auto, 120, 20 );
198 			XmlBeansSettingsImpl settings = getRequest().getSettings();
199 			if( settings.isSet( AUTO_INFER_SCHEMAS ) )
200 			{
201 				auto.setSelected( settings.getBoolean( AUTO_INFER_SCHEMAS ) );
202 			}
203 			auto.addItemListener( new ItemListener()
204 			{
205 				public void itemStateChanged( ItemEvent e )
206 				{
207 					getRequest().getSettings().setBoolean( AUTO_INFER_SCHEMAS, auto.isSelected() );
208 				}
209 			} );
210 			resolveButton = new JButton( "Resolve conflicts" );
211 			resolveButton.setEnabled( false );
212 			resolveButton.setActionCommand( "resolve" );
213 			resolveButton.addActionListener( this );
214 
215 			JXToolBar toolbar = UISupport.createToolbar();
216 			toolbar.addFixed( auto );
217 			toolbar.addFixed( resolveButton );
218 
219 			log = new JLogList( "Schema log" );
220 			conflicts.add( toolbar, BorderLayout.NORTH );
221 			conflicts.add( log, BorderLayout.CENTER );
222 			addTab( "Conflicts", conflicts );
223 
224 			schemaList = new JList( InferredSchemaManager.getInferredSchema( service ).getNamespaces() );
225 			schemaList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
226 			schemaList.addListSelectionListener( this );
227 
228 			toolbar = UISupport.createToolbar();
229 			toolbar.addFixed( UISupport.createToolbarButton( new RemoveNamespaceAction() ) );
230 
231 			JPanel listPanel = new JPanel();
232 			listPanel.setLayout( new BorderLayout() );
233 			listPanel.add( toolbar, BorderLayout.NORTH );
234 			listPanel.add( new JScrollPane( schemaList ), BorderLayout.CENTER );
235 			xsd = JXEditTextArea.createXmlEditor( false );
236 			xsd.setEditable( false );
237 			update();
238 			addTab( "Schemas", new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, listPanel, new JScrollPane( xsd ) ) );
239 		}
240 
241 		public synchronized boolean awaitButton( Handler handler )
242 		{
243 			if( auto.isSelected() )
244 				return false;
245 			resolveButton.setEnabled( true );
246 			this.handler = handler;
247 			return true;
248 		}
249 
250 		public synchronized void actionPerformed( ActionEvent e )
251 		{
252 			if( e.getActionCommand().equals( "resolve" ) )
253 			{
254 				resolveButton.setEnabled( false );
255 				handler.go();
256 			}
257 			else if( e.getActionCommand().equals( "save" ) )
258 			{
259 				InferredSchemaManager.save( service );
260 			}
261 		}
262 
263 		public void propertyChange( PropertyChangeEvent evt )
264 		{
265 			update();
266 		}
267 
268 		public void update()
269 		{
270 			String[] namespaces = InferredSchemaManager.getInferredSchema( service ).getNamespaces();
271 			for( int i = 0; i < namespaces.length; i++ )
272 				if( namespaces[i].equals( "" ) )
273 					namespaces[i] = NO_NAMESPACE;
274 			schemaList.setListData( namespaces );
275 			if( schemaList.isSelectionEmpty() )
276 			{
277 				xsd.setText( "" );
278 			}
279 			else
280 			{
281 				xsd.setText( XmlUtils.prettyPrintXml( InferredSchemaManager.getInferredSchema( service )
282 						.getXsdForNamespace( ( String )schemaList.getSelectedValue() ) ) );
283 				xsd.scrollTo( 0, 0 );
284 			}
285 		}
286 
287 		public void logln( String line )
288 		{
289 			log.addLine( line );
290 		}
291 
292 		public void valueChanged( ListSelectionEvent e )
293 		{
294 			if( e.getValueIsAdjusting() == false )
295 			{
296 				if( !schemaList.isSelectionEmpty() )
297 				{
298 					String namespace = ( String )schemaList.getSelectedValue();
299 					if( namespace.equals( NO_NAMESPACE ) )
300 						namespace = "";
301 					xsd.setText( XmlUtils.prettyPrintXml( InferredSchemaManager.getInferredSchema( service )
302 							.getXsdForNamespace( namespace ) ) );
303 					xsd.scrollTo( 0, 0 );
304 				}
305 			}
306 		}
307 
308 		private class RemoveNamespaceAction extends AbstractAction
309 		{
310 			private RemoveNamespaceAction()
311 			{
312 				putValue( SMALL_ICON, UISupport.createImageIcon( "/remove_property.gif" ) );
313 				putValue( SHORT_DESCRIPTION, "Removes selected inferred namespace definition" );
314 			}
315 
316 			public void actionPerformed( ActionEvent e )
317 			{
318 				if( !schemaList.isSelectionEmpty() )
319 				{
320 					String ns = ( String )schemaList.getSelectedValue();
321 					if( UISupport.confirm( "Remove inferred namespace '" + ns + "'?", "Remove namespace" ) )
322 					{
323 						if( ns.equals( NO_NAMESPACE ) )
324 							ns = "";
325 						InferredSchemaManager.deleteNamespace( service, ns );
326 					}
327 				}
328 			}
329 		}
330 	}
331 
332 	public class Handler implements ConflictHandler, Runnable
333 	{
334 		private SchemaTabs panel;
335 		private XmlObject xml;
336 		private List<String> paths;
337 		private boolean yesToAll = false;
338 		private boolean kill = false;
339 
340 		public Handler( SchemaTabs panel, XmlObject xml )
341 		{
342 			this.panel = panel;
343 			this.xml = xml;
344 			paths = new ArrayList<String>();
345 		}
346 
347 		public synchronized void run()
348 		{
349 			try
350 			{
351 				if( panel.awaitButton( this ) )
352 				{
353 					try
354 					{
355 						wait();
356 					}
357 					catch( InterruptedException e )
358 					{
359 						e.printStackTrace();
360 					}
361 				}
362 				else
363 					yesToAll = true;
364 				if( kill )
365 					return;
366 				InferredSchemaManager.getInferredSchema( service ).learningValidate( xml, this );
367 				panel.update();
368 				setTitle( "Schema" );
369 				InferredSchemaManager.save( service );
370 			}
371 			catch( XmlException e )
372 			{
373 				setTitle( "Schema (invalid)" );
374 			}
375 		}
376 
377 		public synchronized void go()
378 		{
379 			notifyAll();
380 		}
381 
382 		public synchronized void kill()
383 		{
384 			kill = true;
385 			notifyAll();
386 		}
387 
388 		public boolean callback( Event event, Type type, QName name, String path, String message )
389 		{
390 
391 			// if(paths.contains(path)) return true;
392 			StringBuilder s = new StringBuilder( message ).append( " " );
393 			if( event == Event.CREATION )
394 			{
395 				paths.add( path );
396 				s.append( "Create " );
397 			}
398 			else if( event == Event.MODIFICATION )
399 			{
400 				paths.add( path );
401 				s.append( "Modify " );
402 			}
403 			if( type == Type.ELEMENT )
404 				s.append( "element '" );
405 			else if( type == Type.ATTRIBUTE )
406 				s.append( "attribute '" );
407 			else if( type == Type.TYPE )
408 				s.append( "type '" );
409 			s.append( name.getLocalPart() ).append( "' in namespace '" ).append( name.getNamespaceURI() ).append(
410 					"' at path " ).append( path ).append( "?" );
411 			if( !yesToAll )
412 			{
413 				int choice = UISupport.yesYesToAllOrNo( s.toString(), "Conflict" );
414 				if( choice == 2 )
415 				{
416 					panel.logln( s.append( " FAIL" ).toString() );
417 					return false;
418 				}
419 				else if( choice == 1 )
420 					yesToAll = true;
421 			}
422 			panel.logln( s.append( " OK" ).toString() );
423 			return true;
424 		}
425 
426 	}
427 
428 	public RestRequest getRequest()
429 	{
430 		return request;
431 	}
432 }