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