api-monitoring

Creating your own TestSteps in soapUI

This time around let’s do something really low-level and useful: use the soapUI extensions mechanisms to create a custom TestStep for sending an email during the execution of a TestCase. Since we have quite a lot of ground (and code!) to cover, let’s get started.

Short Overview

creating test stepsoapUI internally uses a number of registries containing factories for core objects, for example TestSteps, assertions, desktop panels etc. (see Custom Factories for an extensive list). To create a custom TestStep, we therefore need to supply at least one factory: that for creating the TestStep itself – a TestStepFactory. Additionally (but not required) since we also want to provide a nice panel inside soapUI for editing the actual email and related properties we need to provide another factory for creating the PanelBuilder that creates this editor panel – a PanelBuilderFactory.

soapUI makes heavy use of the apache XMLBeans framework for holding configurations for internal objects – these are generated from the soapui.xsd file included in the soapUI source code. You will see how the state of our TestStep is read and written to objects created for this purpose a bit further down.

Confusing? Don’t worry – and to be honest, looking at this code that has been written over the last seven years, there are many things that could have been done differently (and better for sure) – bear with us!

1. Create and register the TestStep Factory and TestStep class

As mentioned, the TestStep itself is created by a TestStepFactory – a WsdlTestStepFactory to be exact (legacy naming…) – here it is:

public class EMailTestStepFactory extends WsdlTestStepFactory
{
       private static final String EMAIL_STEP_ID = "email";
       public EMailTestStepFactory()
       {
              super( EMAIL_STEP_ID, "EMail TestStep", "Sends an email", "email.png" );
       }
        public WsdlTestStep buildTestStep( WsdlTestCase testCase, TestStepConfig config,
 boolean forLoadTest )
       {
                return new EMailTestStep( testCase, config, forLoadTest );
       }
       public TestStepConfig createNewTestStep( WsdlTestCase testCase, String name )
       {
              TestStepConfig testStepConfig = TestStepConfig.Factory.newInstance();
              testStepConfig.setType( EMAIL_STEP_ID );
              testStepConfig.setName( name );
              return testStepConfig;
        }
       public boolean canCreate()
        {
               return true;
        }
}

Let’s have a quick look at the methods:

  • The constructor invokes the super constructor with a number of basic properties, including the unique ID for the TestStep and the path to an icon for representing the TestStep (thanks for FamFamFam!). The path can be either absolute or relative to the current working directory.
  • The createNewTestStep method is called by soapUI when the user selects to create a new TestStep of our type, it returns an XmlBeans TestStepConfig (from the corresponding TestStep complex type in the soapUI XSD) that will be the basis for the state of the TestStep and its configuration
  • The buildTestStep method is called to create the actual TestStep – it is passed the containing TestCase (TestSteps must always be in a TestCase) and the corresponding TestStepConfig object that was at some time created by the above createNewTestStep method.
  • The canCreate method simply tells soapUI that the factory is working ok – here you could add a check of some external conditions required for this factory to be active, for example a license-check.

Of course we also need to create the EMailTestStep class returned by the buildTestStep method:

public class EMailTestStep extends WsdlTestStepWithProperties
{
       protected EMailTestStep( WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest )
       {
                 super( testCase, config, true, forLoadTest );

                 if( !forLoadTest )
                 {
                         setIcon( UISupport.createImageIcon( "email.png" ) );
                 }           
        }

        @Override
        public TestStepResult run( TestCaseRunner testRunner, TestCaseRunContext context )
        {
               WsdlTestStepResult result = new WsdlTestStepResult( this );
               result.setStatus( TestStepStatus.OK );
               return result;
        }
 }

This is almost the simplest possible implementation;

  • The constructor passes all arguments to its super-class and initializes the icon if we are not running under a LoadTest (either in soapUI or loadUI) – when the icon isn’t needed anyway.
  • The run method is what is called by soapUI during TestCase execution – our default implementation just creates a default result object, sets its status to OK and returns it.This is where we will do the actual sending of the email a bit further down.

These two classes are actually enough to get things started in soapUI, all that is missing is an xml configuration file that adds the factory to soapUI – it should be placed in the soapUIbinfactories folder and named anything ending with -factories.xml (we will call it email-teststesp-factories.xml). It contains the following:

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-factories xmlns:tns="http://eviware.com/soapui/config">
     <tns:factory id="EMailTestStep" 
  factoryType="com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepFactory" 
  factoryClass="soapui.demo.teststeps.email.EMailTestStepFactory"/>

</tns:soapui-factories>

As you can see this simply registers our factory of type WsdlTestStepFactory with soapUI.

Great! After compiling and packaging these classes into a jar file and dropping them into the soapUIbinext folder together with the factories xml file in the factories folder we can start soapUI – and the magic begins!

On startup you should see the following rows in the soapUI log at the bottom of the soapUI window:

Wed Jan 25 00:03:51 CET 2012:INFO:Adding factories from [C:..factoriesemail-teststep-factories.xml]

Wed Jan 25 00:03:51 CET 2012:INFO:Adding factory [class soapui.demo.teststeps.email.EMailTestStepFactory]

Awesome! Now if we create an empty project and just add a TestSuite and TestCase we will see the following in the TestCase window:

teststeps-toolbar

And pressing that button (or using the right-click menu in the empty list of TestSteps ) allows us to add an Email TestStep:

Created-teststep

Finally, running the TestCase will show the following in the TestCase Run Log at the bottom of the TestCase window:

initial-testrun-log

Great! Of course we can save / reload the project, add more TestSteps, move them around, clone them, etc. – everything that the soapUI infrastructure allows now also supports our (totally useless) TestSep.

As a last improvement before we add the desktop panel, let’s fix the right-click menu of the TestStep. Currently it looks like this:

Original-right-click-menu

This is missing some key actions, including the actions to open the TestSteps editor panel (which will create below), enable/disable the TestStep, get help, etc. < /p>

What we need to do is create a corresponding popup menu definition for our TestStep in a custom email-teststep-actions.xml file that we save to the soapUIbinactions folder (which plugs into the actions infrastructure in soapUI):

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-actions xmlns:tns="http://eviware.com/soapui/config">
 <tns:actionGroup name="EMail TestStep Actions"
            id="EMailTestStepActions"
class="com.eviware.soapui.impl.wsdl.actions.teststep.WsdlTestStepSoapUIActionGroup">
              <tns:actionMapping actionId="SeperatorAction" />
              <tns:actionMapping actionId="ShowOnlineSoapUIHelp"
                    keyStroke="F1" param="http://blog.smartbear.com" />
      </tns:actionGroup>
</tns:soapui-actions>

Here we define a soapUI “action-group” which is used to populate the popup menu above – the id of the group matches the actual name of the TestStep class and it uses a custom SoapUIActionGroup implementation internally (which adds these actions and an “Open Editor” action to the popup you can see above).  We only add a custom Separator and Online Help action – the custom WsdlTestStepSoapUIActionGroup does the rest. Save the file and restart soapUI – you will now see the following row in the soapUI log:

INFO:Adding actions from [C:..actionsemail-teststep-actions.xml]

Now that we have this file registered the right-click menu looks a little nicer:

 

Time to move on!

2. Create a PanelBuilderFactory and its DesktopPanel

The next step is to create a similar skeleton for the desktop panel that we want to show to our users when they double-click the TestStep (which for now does nothing). Once again we will create a Factory, this time for a PanelBuilder that is used by soapUI to build both the main desktop panel and the properties panel in the bottom left for our new TestStep. Let’s start with the Factory and Builder first:

public class EMailTestStepPanelBuilderFactory implements 
PanelBuilderFactory<EMailTestStep>
{
     @Override
     public PanelBuilder<EMailTestStep> createPanelBuilder()
     {
            return new EMailTestStepPanelBuilder();
     }

     @Override
     public Class<EMailTestStep> getTargetModelItem()
     {
            return EMailTestStep.class;
     }

     public static class EMailTestStepPanelBuilder extends 
EmptyPanelBuilder<EMailTestStep>
      {
             @Override
             public DesktopPanel buildDesktopPanel( EMailTestStep modelItem )
             {
                    return new EMailTestStepDesktopPanel( modelItem );
             }

             @Override
             public boolean hasDesktopPanel()
             {
                    return true;
             }
       }

}

This is once again pretty straight forward – the EMailTestStepPanelBuilderFactory creates the corresponding PanelBuilder in the createPanelBuilder method, and the EMailTestStepPanelBuilder is implemented as an inner class which simply creates the corresponding EMailTestStepDesktopPanel when asked to. Also, it overrides the hasDesktopPanel() method to return true to let soapUI know that it has this functionality.

Of course we need to create the actual EMailTestStepDesktopPanel returned by the PanelBuilder:

public class EMailTestStepDesktopPanel extends ModelItemDesktopPanel<EMailTestStep>
{
  public EMailTestStepDesktopPanel( EMailTestStep modelItem )
  {
         super( modelItem );
  }
}

No frills for now – very simple – we’ll spice it up further down the line.

Just as for the TestStep Factory, we need to add this PanelBuilder Factory to our email-teststep-factories.xml file, which now contains two factories:

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-factories xmlns:tns="http://eviware.com/soapui/config">
    <tns:factory id="EMailTestStep" 
   factoryType="com.eviware.soapui.impl.wsdl.teststeps.registry.WsdlTestStepFactory" 
   factoryClass="soapui.demo.teststeps.email.EMailTestStepFactory"/>   

    <tns:factory id="EMailTestStepPanelBuilder" 
   factoryType="com.eviware.soapui.model.util.PanelBuilderFactory" 
   factoryClass="soapui.demo.teststeps.email.EMailTestStepPanelBuilderFactory"/>   
</tns:soapui-factories>

Great! Package this up and restart soapUI – the soapUI log will now contain:

INFO  [DefaultSoapUICore] Adding factories from [C:..email-teststep-factories.xml]
INFO  [DefaultSoapUICore] Adding factory [class soapui.demo.teststeps.email.EMailTestStepFactory]
INFO  [DefaultSoapUICore] Adding factory [class soapui.demo.teststeps.email.EMailTestStepPanelBuilderFactory]

Now double-clicking on one of our Email TestSteps will open an empty window:

And in the bottom left you can see the default table of properties created by the base EmptyPanelBuilder class. Try changing the name of the TestStep there and watch what happens to the title in the opened panel. Fabulous!

3. Implement configuration handling

Now that we have the core plumbing of our TestStep in place let’s add some actual logic. First we need to define the properties that our TestStep will use:

  • Subject – the subject of the email message to send
  • Message – the actual message content
  • Server – which SMTP server to use
  • MailFrom – the from address
  • MailTo – and the to address

(Of course this could be extended with many more – I’ll leave that to you).

We also need to add code for reading/writing these properties from/to the underlying XMLBeans configuration object, main points to adding this are as follows:

private String subject;
private String message;
private String server;
private String mailTo;
private String mailFrom;

…

private void readConfig( TestStepConfig config )
{
        XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( config.getConfig() );
        subject = reader.readString( "subject", "" );
        message = reader.readString( "message", "" );
        server = reader.readString( "server", "" );
        mailTo = reader.readString( "mailTo", "" );
        mailFrom = reader.readString( "mailFrom", "" );
}

private void updateConfig()
{
         XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
         builder.add( "subject", subject );
         builder.add( "message", message );
         builder.add( "server", server );
         builder.add( "mailTo", mailTo );
         builder.add( "mailFrom", mailFrom );
         getConfig().setConfig( builder.finish() );
}

public String getSubject()
{
       return subject;
}

public void setSubject( String subject )
{
       String old = this.subject;
       this.subject = subject;
       updateConfig();
       notifyPropertyChanged( "subject", old, subject );
}

This is pretty straight-forward:

  • We define the properties as private fields
  • We add a “readConfig” method that is used to read the values in the configuration into our properties
  • We add an “updateConfig” method that saves all properties back to the underlying XMLBeans configuration object
  • We call this updateConfig method in all property setters before notifying property changes.

4. Add UI

Of course there wouldn’t be much fun if we couldn’t edit these fields in the DesktopPanel that we created previously – I’ll save you the code (download it below) for now – the result is as follows:

This is probably the messiest code to get to grips with as it uses a lot of internal soapUI utility classes for building Swing interfaces (forms, etc) – you are of course in no way forced to use those, if you prefer to use your own Swing components you are free to do just that (or how about embedding JavaFX 2?).

5. Implement run method

Almost there now – we just need to add the actual code for sending the email to our run method in the TestStep:

 

public TestStepResult run( TestCaseRunner testRunner, TestCaseRunContext context )
{
       WsdlTestStepResult result = new WsdlTestStepResult( this );
       result.startTimer();

       try
       {
             Properties props = System.getProperties();
             props.put( "mail.smtp.host", context.expand( server ) );

             Session session = Session.getDefaultInstance( props, null );
             Message msg = new MimeMessage( session );
             msg.setFrom( new InternetAddress( context.expand( mailFrom ) ) );
             msg.setRecipients( Message.RecipientType.TO, InternetAddress.parse( 
 context.expand( mailTo ), false ) );
             msg.setSubject( context.expand( subject ) );
             msg.setText( context.expand( message ) );
             msg.setHeader( "X-Mailer", "soapUI EMail TestStep" );
             msg.setSentDate( new Date() );

             Transport.send( msg );
             result.setStatus( TestStepStatus.OK );
        }
        catch( Exception ex )
        {
               SoapUI.logError( ex );
               result.setError( ex );
               result.setStatus( TestStepStatus.FAILED );
         }

         result.stopTimer();
         return result;
}

This is actually very straight forward javamail code – fortunately soapUI already includes the required libraries (it needs them for mime attachment handling). Now if we send the above mail by running the TestCase we get the following in the log:

and eventually the following in our mailbox:

Sweet :)

One thing to note is that we used context.expand( <field> ) to assign the corresponding properties on the MimeMessage object; this expands any property-expansions in the corresponding values, thus effectively adding property-expansion support to all fields in the TestStep. This does come at a small price though; the TestStep needs to implement the PropertyExpansionContainer interface which is used by the refactoring engine in soapUI to update property-expansions when properties or contracts are updated. Fortunately this interface only mandates one method:

 

public PropertyExpansion[] getPropertyExpansions()
{
       List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();
       result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "subject" ) );
       result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "message" ) );
       result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "server" ) );
       result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "mailTo" ) );
       result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, this, "mailFrom" ));
       return result.toArray( new PropertyExpansion[result.size()] );
}

This is easy to implement thanks to a bunch of internal utility methods used for extracting the PropertyExpansions in our TestStep.

Let’s put the property-expansion support to test:

This delivers the following mail when running the TestCase:

Finally let’s check how errors are handled – enter a bogus mailHost address and run the TestCase. You should get the following:

6. Wrap-up

That’s it! We’ve created a simple Email TestStep showing how easy it is to extend soapUI with new functionality. Let’s recap:

- We created a custom WsdlTestStepFactory which provides our new EMailTestStep

  • EmailTestStepFactory.java
  • EMailTestStep.java

- We created a custom PanelBuilderFactory which provides the PanelBuilder used by soapUI to build the desktop and property panels in the UI

  • EMailTestStepPanelBuilderFactory.java

-We created the EMailTestStepDestopPanel (provided by our EMailTestStepPanelBuilder) that provides the actual UI for the EMail TestStep

  • EMailTestStepDesktopPanel.java

-We created two configuration files – one for registering these two factories and one for the right-click menu of the TestStep

  • email-teststep-factories.xml
  • email-teststep-actions.xml

-We added an icon for the TestStep to the root folder of soapUI

  • email.png

That makes seven files all in all – not too bad – when packaging we need to:

  • compile the java files into a jar that we drop into the soapUIext folder
  • put the factories xml file in the soapUIfactories folder
  • put the actions xml file in the soapUIactions folder
  • put the png in the root folder

An obvious improvement in soapUI would be to support a “plugin-file” format which packs all these into just one file – something for a future release. :)

Next Steps

This was obviously a very basic example; a lot can be improved:

  • Add more configuration possibilities for the actual email; attachments, multiple email addresses, etc.
  • Add a Run button to the panel to allow instant sending of emails.
  • Add support for sending HTML emails.
  • Add support for a script that decides if the email should be sent at all
  • etc…

Download the zip for the source files, configuration files and email.png icon and use it as an inspiration to create your own TestSteps – and please don’t hesitate to mail any TestSteps you create to us so we can publish them on the soapUI website for everyone to enjoy and use!

Also please download the latest nightly build to have all the latest fixes (some related to icon handling in the above example) – and don’t hesitate to get in touch in the forum if you have any questions or issues.

As always – Thanks for your time!

/Ole

SmartBear Software

 See also:


  • jsong

    I tried the first step “Create and register the TestStep Factory and TestStep class”, but cannot see the Email button on test steps tab.

  • http://www.smartbear.com Ole Lensmar

    Hi jsong, 
     
    Which soapUI version are you using? Do you see the extensions being picked up in the soapui log?  
     
    regards! 
     
    /Ole 
    SmartBear Software

  • jsong

    Hey Ole, 
     
    After changed to use 4.5.0(64bit),the issue has been fixed. 
     
    Thanks Ole. 
     
    jsong

  • http://agilizate.blogspot.com adrian

    Hey Ole, 
     
    Quite interesting, and nice, article. I found some issues that I can explain this way: 
     
    a) Under my Fedora 17 I was unable to get read the factories. I tried with same .xml and same .jar under windows and works. xtrange… 
     
    b) I had trouble with step 2, desktop panel doesn’t appear. So I got your code… nothing happens. More extrange as I got latest nightly build 4.5.1.1-snapshot.  
     
    Sure can be a trouble on my side, but till now i don’t get the root cause of my issues… 
     
    Thanks

  • http://www.smartbear.com Ole Lensmar

    Hi Adrian, 
     
    hmm.. do you get any errors in the error log tab at the bottom of the soapUI window!? 
     
    regards! 
     
    /Ole 
    SmartBear Software

  • http://agilizate.blogspot.com adrian

    Ole, 
     
    No errors at all. So I’ve no clue what’s going on that’s reason for using your code instead of mine…

  • Jeff

    Great idea! I’m just starting with this and have a few questions: 
     
    1. I assume the .java files need to be compiled into .class files first before putting them in the .jar? 
     
    2. Does the .jar need to have a specific name? 
     
    3. My soapUI installation is here: “C:Program FilesSmartBearsoapUI-Pro-4.5.1″. Is this considered the “root” directory so I can put the email.png file there? 
     
    Thanks for your help!

  • Rao

    Hi Ole, 
     
    Like this article as it put things together for some one who is interested in extending. Thank you for the details steps. 
     
    I use ubuntu 11.10, 64 bit, with soapui-4.5.1. Got your code(zip file), compiled using soapui-4.5.1 libraries, and placed the files appropriately as mentioned(copied email.png in $SOAPUI_HOME). 
     
    From the soapui.log, it is noticed that extension jar, actions xml are loading, but not factories. When soapui is opened email step is not shown either. 
     
    Not sure what is wrong. No error in soapui-error.log too. 
     
    Any thing to look at? or missing something?  
    Thanks in advance, 
    Regards, 
    Rao.

  • http://freejava.webuda.com Adrian

    Rao that’s what happened to me. 
     
    I found the same error, and I tried same code on Windows… that worked. Why? 
     
    Still didn’t put my Test Step working :-(

  • Rao

    I just saw your reply Adrian. 
     
    Gave jar that i build and other xmls, png file to my friend, and able see the email test step on windows 7 
     
    Looking at Adrain post, then i realized that, the common pattern is linux. Since soapui is written is written in java, it is platform independent. Then looked at soapui.sh, there is the problem while loading. 
     
    The following is missing 
    JAVA_OPTS=”$JAVA_OPTS -Dsoapui.ext.factories=$SOAPUI_HOME/bin/factories” 
     
    Then restarted soapui, i can see the email step though image is not shown. 
     
    Now i will try if sends email. 
     
    Regards, 
    Rao.

  • Rao

    Able to send email successfully. 
     
    Figured out that email.png should be copied under $SOAP_HOME/bin 
     
    Thank you Ole for such nice article.

  • Rao

    Thank you Ole for taking care of that. 
     
    I would like to create test step for sending a JMS message (not soap over jms), along with properties defined by user(like how custom properties defined for a test case) along with data type and values. 
     
    Here am not sure, how ui can be build here, which Panel is the right option etc?  
     
    Familiar with heremesJMS, but not sure if this can be used for my purpose. 
     
    Appreciate your help in this regard. 
     
    Thank you, 
    Rao.

  • Ole Lensmar

    Hi Rao 
     
    Thanks for clearing this up – I’ve added a fix request for adding the missing option to the .sh files 
     
    Looking forward to your TestSteps – Good Luck! 
     
    /Ole 
    SmartBear Software

  • Rao

    Thank you Prashant, sent the panel wireframes across to you on the email provided. Looking forward for the suggestions in this regard.

  • Rao

    Created mock ui panels for clarity that i was talking about, i don’t know how to share them.

  • http://blog.smartbear.com Prashant Kaw

    Hi Rao, 
    You can email them to prashant dot kaw at smartbear and I can get them over to Ole. 
    /Prashant 
    @bloggerbear 

  • Jamie

    Excellent tutorial – thank you very much. Question with regards to it – how can you make an item within the test step selectable from a property transfer? 
     
    So if you select a REST request you can select “Request” in a property transfer and it will transfer to the request message. Is there a way to make “message” appear in a property transfer dropdown? 
     
    Many thanks 
    Jamie

  • http://www.smartbear.com Ole Lensmar

    Hi Jamie, 
     
    you need to add “message” as a property; derive your TestStep from WsdlTestStepWithProperties and call the addProperty method in your constructor – the added properties will be exposed to property-transfers, etc.  
     
    Have a look at the iniRequestProperties method in WsdlTestRequestStep to see how this can be done (http://www.soapui.org/xref/com/eviware/soapui/impl/wsdl/teststeps/WsdlTestRequestStep.html#125). 
     
    Hope this helps! 
     
    /Ole 
    SmartBear Software

  • http://www.smartbear.com Ole Lensmar

    Hi Rao, 
     
    You need to build the Swing UI for your JMS TestStep using standard Swing component (JPanel, etc..) added to the base JPanel – use the EMailTestStepDesktopPanel in the example above as your starting point. 
     
    Good Luck! 
     
    /Ole 
    SmartBear Software

  • Jamie

    Thanks Ole – I’ve now got a property that I can choose. However, if I transfer to this property – it doesn’t update. And if I type directly into the property it goes blank. What have I done wrong? 
     
    Thanks 
    Jamie

  • http://nizarellouze@yahoo.fr Nizar

    Hi, 
     
    Could you tell me where I can find depending jar, WsdlTestStepFactory and many other classes are not regonized

  • http://java67.blogspot.jp Java Blog

    Where can I use this ?

  • http://jie.song@rovicorp.com jsong

    hi Ole, 
     
    Could you confirm it works well on soapui version 4.0? 
     
    Thanks

  • Klusht

    Great article , like it very much. It proves you can help me with one small issue as you know a lot :)
    I created a jar file with a class full of static functions, and after I deploy it in ext folder, I can use it like valx = new functions.F(log).time() ( package functions , class F with constructor F(log4j.Logger log) to be used in the class and method time() ). 
    But is ugly, and I need initialize this class ( functions.F(log) ) under a nicer shorter name, like context.setProperty(“f”, new functions.F(log)) ( then to be used as context.f.time()). 
    So I nee a place where to put this initialization step, and where soapUI executes this line at sturtup , or before lunching the testRunner. 
     
    I’ve tryed to put in in the project custom property something like :addFunctions = ${=context.setProperty(“f”, new functions.F(log))} but is not executed by default, so is available only if I run this statement inside the testStep. 
     
    Can you suggest me a second place where I can put statements that are runned before are test step… 
     
    Also , is tere a way to put a script precondition IN the testStep. like an “pre assertion”

  • SiKing

    You mentioned:
    “An obvious improvement in soapUI would be to support a “plugin-file” format which packs all these into just one file – something for a future release.”
    Has this been considered yet?